Рубріки: Мнение

«Я жалею, что писал на нем»: разработчик объяснил, почему PHP — не конкурент Java и C#

Оленка Пилипчак

«Я сожалею, что писал на PHP», — написал разработчик Джордж Кастро из Чили. Совсем недавно он закончил большой серьезный проект (который был около трех лет в разработке), и этот проект даже работает так, как было задумано. Тем не менее, совсем не кажется надежным.

Джордж уверен, что теряет слишком много времени на другие задачи, а не на собственный кодинг (включая тестирование/бенчмаркинг, создание собственных библиотек).

Передаем ему слово.


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

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

Тогда было принято решение создать ORM с нуля и достичь двух основных целей:

  • простоты;
  • быстродействия.

А это тот самый проект, который получился в итоге. И он работает!

Зачем мне ORM?

Невозможно создать проект, используя примитивные функции PHP (в данном случае PDO), например:

1) с использованием PDO (есть склонность к ошибкам):

$stmt = $pdo->prepare("SELECT * FROM myTable WHERE name = ?");
$stmt->bindParam(1,$_POST['name'],PDO::PARAM_STR);
$stmt->execute();
$result = $stmt->get_result();
$products=[];
while($row = $result->fetch_assoc()) {
  $product[]=$row; 
}
$stmt->close();

2) с использованием моей библиотеки:

$products=$pdoOne
    ->select("*")
    ->from("myTable")
    ->where("name = ?",[$_POST['name']]) 
    ->toList();

3) с использованием моей библиотеки (ORM):

ProductRepo // this class was generated with echo $pdoOne()->generateCodeClass(['Product']);
    ::where("name = ?",[$_POST['name']])
    ::toList();

Основная проблема, которую я обнаружил в PHP — это динамическая типизация.

Динамическая типизация

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

$products=ProductRepo::listall();

Но PHP не знает, возвращаем ли мы список товаров. Мы могли бы добавить type hinting, но это всего лишь проверка типа и ничего более. Type hinting — дешевое решение и оно ненадежно.

function listall():array {
    // code goes here
 }

Но есть еще одна проблема. В PHP нет концепции array-of-a-specific-type.

Это Java:

products=new ArrayList<Product>();

Это С#:

products=new List<Product>();

А это PHP:

$products=array(); // или просто [], мы могли бы хранить здесь объект, число или любое другое значение.

Массивы VS объекты

Допустим, у нас есть модель инвойса:

class InvoiceDetails {
   public $idInvoiceDetail;
   public $idInvoice;
   public $product;
   public $quantity;
}
class Invoice {
   public $idInvoice;
   public $customer;
   public $date;
   public $invoiceDetails;  // список деталей инвойса
}

Если мы хотим произвести сериализацию:

class InvoiceDetails {
   public $idInvoiceDetail;
   public $idInvoice;
   public $product;
   public $quantity;
}
class Invoice {
   public $idInvoice;
   public $customer;
   public $date;
   public $invoiceDetails;  // список деталей инвойса
}

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

Лучше решение — использование ассоциативного массива. Почему?

$invoice=['idinvoice'=>1...]; // $invoice это массив
$json=json_encode($invoice); // $json это сериализованный массив
$invoiceback=json_decode($json,true); // сейчас $invoice это массив.

В PHP нам вообще не нужно использовать модели. Мы могли бы использовать некоторые из них для type hinting, но это неидеальное решение, оно ненадежно и не поможет в большом проекте. Это также значительно снизит производительность (в основном при сериализации/десериализации).

Библиотеки — это решение?

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

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

Так почему же я сожалею об использовании PHP?

Допустим, наша первоначальная задача — функция, которая возвращает список товаров.

Это PHP:

$products=ProductRepo::listAll();
   // show the first product if any
   if($products!==null && count($products)>0) {
      echo $products[0]->name; // or $products[0]['name'] if array
   }

Нет гарантии, что этот код будет работать, потому что язык PHP на самом деле не знает, что $products содержит список товаров.

Это С#:

  List<Product> products=ProductoRepo.listAll();
   if(products!=null && products.Count()>0) {
      console.log(products[0].name) 
   }

Код всегда будет работать, потому что С# знает, что переменная products представляет собой список товаров (или null), и другой альтернативы нет.

Это реализация функции в С#:

function listAll() {
    var result=new List<Product>();
    using(var dbase=new BaseExample()) {
        result=dbase.Products.toList();
    }
    return result;

Здесь используется фреймворк Entity, он считывает из базы данные, конвертирует значение и возвращает список товаров. Падение производительности минимальное (я тестировал). Почему?

Во-первых, системе не нужно гадать (либо все работает, либо выдает ошибку), не нужно жонглировать типами. Во-вторых, C# — компилируемый язык. И в этом его преимущество. Это просто и это работает!

Это сериализация/десериализация:

string json = JsonSerializer.Serialize(products);
var productsdes = JsonSerializer.Deserialize<List<Product>>(json);

И снова все просто: это или работает или выдает ошибку (золотой середины нет).

В качестве комментариев

О Swoole

Я протестировал Swoole, он работает, но возникают две проблемы.

Не работает в Windows

Нельзя сказать ничего плохого о Windows — это основа для многих корпораций и крупных компаний. Имею в виду, например, офисный пакет и другие программы вроде VPN, которые работают только в Windows. Я разрабатываю проекты в Windows, потому что на этой системе работают почти все программы (есть три исключения, одно из них — Swoole).

P.S. Вторая программа, которая не работает в Windows — Docker. Существует версия Docker для Windows, но это «крошечный» эмулятор. Это не легкий контейнер, а громоздкий (он в принципе ломает весь смысл существования контейнера).

Никакого волшебства в Swoole нет

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

И в этом случае C# изначально решает асинхронные задачи. Поэтому нам не нужно запускать отдельную службу, чтобы выполнить все медленные задачи одновременно в отдельном потоке (и без создания сокетов). Microsoft предупреждает о том, когда мы можем использовать его, а когда нет. Многозадачность — это не волшебная палочка на все случаи жизни.

Почему я использую PHP 5.x?

А вот и нет. Я использую PHP 8.0.10. Тем не менее, я могу поддерживать синтаксическую совместимость своего кода с PHP 5.6 (по крайней мере, библиотеки с открытым исходным кодом). В PHP 7 и 8 есть несколько новых синтаксисов, но большинство из них — синтаксический сахарСинтаксические возможности, применение которых не влияет на поведение программы, но делает использование языка более удобным, поэтому не стоит переносить эти общедоступные библиотеки с версии 5.6 на 7 или 8. Почему?

Здесь есть несколько полезных функций, но не более того. Мне нравится OPcache PHP > 7.0.

Type hinting

Одна из новых возможностей PHP 7 — type hinting. И это еще одна сложность.

В PHP type hinting одновременно и type validation. Валидация проверяется не один раз, а каждый раз, когда вызывается функция:

// what you write
function example(Class $field1): string {
}
// what php really does:
function example($field1) {
    if(!get_type($gettype)!='Class') {
        throw new Exception("field incorrect");
    }
    // ...    
}

К счастью, type hinting нужно всего 0,0001 секунды на 5000 запросов. Но медлительность накапливается (например, 1000 пользователей одновременно используют 5000 строк, каждая строка вызывает 1 функцию). Это влияет на производительность. В большей части кода это не имеет большого значения, но некоторые функции вызываются не один раз, а тысячу по запросу пользователя.

Например, если у нас есть функция, на выполнение которой нужно 0,001 секунды и она вызывается 100 раз, то в сумме функции необходимо 0,1 секунды. Это не так уж плохо, но это мера для каждого запроса, поэтому, если у нас есть 100 запросов одновременно, получается уже 10 секунд.

Если разделить на 4 ядра, то каждое будет работать по 2 секунды. А это уже не совсем хорошо.

Я активно использую PHPDocАдаптированный стандарт документирования Javadoc для использования в PHP, так как он не влияет на производительность и еще более эффективен, чем type hinting. Он неидеален, но дает некий баланс между производительностью и хинтингом.

Python в этом аспекте более прямолинеен. В Python type hinting — это всего лишь hint, а не валидация, поэтому валидация кода зависит от самого кода.

В C# валидация выполняется при компиляции и только один раз. Что касается PHP — это происходит при каждом вызове.

Psalm и PHPStan

Обе эти библиотеки хорошие, но они не бесплатны (добавляют новый «этап компиляции»). И они не творят чудес. С их помощью нет возможности обнаружить абсолютно все при нетипичных проблемах.

Потому я не беру в расчет Psalm и PHPStan. PHPStorm выполняет многие задачи обеих библиотек, не замедляя процесс разработки.

Обе библиотеки даже не в состоянии определить правильный тип поля переменной (если переменная достаточно сложная).

В C# есть несколько библиотек, выполняющих ту же работу. Тем не менее, Visual Studio (не код) также превосходно обнаруживает проблемы сразу после запуска и позволяет рефакторить.

Как итог

PHP — не плохой язык. Если посмотреть на Python и Django (или Ruby и Rails), PHP намного лучше. Но он все еще не конкурент Java и C#.

Язык PHP играет в игру с динамической типизацией, пытается имитировать статический типизированный язык. Возможно, PHP нужно разветвить и создать новый язык статического типа.

PHP и синдром самозванца

Что я увидел:

class Customer {
     private $id;
     private $name;
     public function setId(int $id) {
        $this->id=$id;
     }
    public function getId():int {
        return $this-$id;
    }
}

Производительность этого фрагмента кода ужасна. С таким кодом Java может работать, потому что оптимизирует сеттер и геттер. У PHP нет такой функции.

Что насчет Laravel:

class Customer extends Models {
}
// O_O

Нам нужно угадать пользовательские поля. Мы можем добавить hinting и значения по умолчанию, но не более того. Кроме того, эта модель полагается на рефлексию (__set method). Laravel основан на этом коде, а модель — это ядро для каждой функциональности кода, это и есть причина его медленной работы.

Да и Symphony не лучше. Например, использование YAML (у него есть некоторые альтернативы):

Article:
  actAs: [Timestampable]
  tableName: blog_article
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
    title:   string(255)
    content: clob

И WordPress ничем не лучше:

$customers=['id'=1,'name'=>'john'];

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

Но почему С?

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

Также, если взглянуть на рабочие места в США:

  • Python — 176k вакансий;
  • Java — 142k вакансий;
  • Javascript — 119k вакансий;
  • C# — 83k вакансий;
  • PHP — 15k вакансий.

Но это просто еще одна неполная мера.

Например, Python более популярен, чем Java, но Java-разработчик дольше задерживается на рабочем месте (проекты больше и высоко оплачиваются).

На заметку

Многие разработчики имеют неправильное представление о производительности и юзабилити. Пример: «Мой код медленный, но если я добавлю какой-нибудь фреймворк или библиотеку, он автоматически будет работать лучше».

Некоторые библиотеки, а особенно — фреймворки, не подходят для повышения производительности. Они больше сосредоточены на стабильности, валидации и общем использовании (и некоторые из них в основном синтаксический сахар).

В Laravel, чтобы назначить поле, мы прописываем:

$object->field='hello';

Но что делает фреймворк:

  • проверяет, есть ли у поля мутаторы (если да, то применяет);
  • проверяет, является ли поле датой;
  • проверяет, является ли поле вызываемым;
  • проверяет, является ли поле json castable;
  • проверяет, является ли поле зашифрованным;
  • и, наконец, присваивает значение внутреннему массиву.

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

public static function studly($value)
    {
        $key = $value;
        if (isset(static::$studlyCache[$key])) {
            return static::$studlyCache[$key];
        }
        $value = ucwords(str_replace(['-', '_'], ' ', $value));
        return static::$studlyCache[$key] = str_replace(' ', '', $value);
    }

Каждый раз.

В этом и проблема:

for($i=0;$i<10000;$i++) {
   $object[$i]->field='hello'; // bye bye performance
}

Перевод: Ольга Змерзлая

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023