Browse Source

[php] Slim to v4, php8.1 and adapterman (#7660)

* [php] Slim to v4

* Update to v4

* Add with adapterman
Joan Miquel 2 years ago
parent
commit
48a435fe59

+ 44 - 0
frameworks/PHP/slim/benchmark_config.json

@@ -23,6 +23,50 @@
       "display_name": "slim",
       "notes": "",
       "versus": "php"
+    },
+    "workerman": {
+      "plaintext_url": "/plaintext",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/dbs?queries=",
+      "update_url": "/updates?queries=",
+      "fortune_url": "/fortunes",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "MySQL",
+      "framework": "slim",
+      "language": "PHP",
+      "flavor": "PHP8",
+      "orm": "Raw",
+      "platform": "workerman",
+      "webserver": "none",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "slim-workerman",
+      "notes": "",
+      "versus": "php"
+    },
+    "workerman-pgsql": {
+      "db_url": "/db",
+      "query_url": "/dbs?queries=",
+      "update_url": "/updates?queries=",
+      "fortune_url": "/fortunes",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "postgres",
+      "framework": "slim",
+      "language": "PHP",
+      "flavor": "PHP8",
+      "orm": "Raw",
+      "platform": "workerman",
+      "webserver": "none",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "slim-workerman-pgsql",
+      "notes": "Optimized for Workerman",
+      "versus": "php"
     }
   }]
 }

+ 10 - 2
frameworks/PHP/slim/composer.json

@@ -1,6 +1,14 @@
 {
     "require": {
-        "slim/slim": "3.12.4",
-        "slim/php-view": "3.2.0"
+        "slim/slim": "^4.10",
+        "slim/php-view": "3.2.0",
+        "slim/psr7": "1.*",
+        "slim/http": "1.*",
+        "php-di/php-di": "^6.4"
+    },
+    "autoload": {
+        "psr-4": {
+            "Db\\": "db/"
+        }
     }
 }

+ 92 - 0
frameworks/PHP/slim/db/Raw.php

@@ -0,0 +1,92 @@
+<?php
+namespace db;
+
+use PDO;
+use PDOStatement;
+
+class Raw
+{
+    private static PDO $instance;
+    public static PDOStatement $db;
+    public static PDOStatement $fortune;
+    public static PDOStatement $random;
+    /**
+     * @var []PDOStatement
+     */
+    private static $update;
+
+    public static function init()
+    {
+        $pdo = new PDO(
+            'pgsql:host=tfb-database;dbname=hello_world',
+            'benchmarkdbuser',
+            'benchmarkdbpass',
+            [
+                PDO::ATTR_DEFAULT_FETCH_MODE  => PDO::FETCH_ASSOC,
+                PDO::ATTR_ERRMODE             => PDO::ERRMODE_EXCEPTION,
+                PDO::ATTR_EMULATE_PREPARES    => false
+            ]
+        );
+
+        self::$fortune   = $pdo->prepare('SELECT id,message FROM Fortune');
+        self::$random    = $pdo->prepare('SELECT id,randomNumber FROM World WHERE id = ?');
+        self::$instance  = $pdo;
+    }
+
+    /**
+     * Postgres bulk update
+     *
+     * @param array $worlds
+     * @return void
+     */
+    public static function update(array $worlds)
+    {
+        $rows = count($worlds);
+
+        if (!isset(self::$update[$rows])) {
+            $sql = 'UPDATE world SET randomNumber = CASE id'
+                . str_repeat(' WHEN ?::INTEGER THEN ?::INTEGER ', $rows)
+                . 'END WHERE id IN ('
+                . str_repeat('?::INTEGER,', $rows - 1) . '?::INTEGER)';
+
+            self::$update[$rows] = self::$instance->prepare($sql);
+        }
+
+        $val = [];
+        $keys = [];
+        foreach ($worlds as $world) {
+            $val[] = $keys[] = $world['id'];
+            $val[] = $world['randomNumber'];
+        }
+
+        self::$update[$rows]->execute([...$val, ...$keys]);
+    }
+
+    /**
+     * Alternative bulk update in Postgres
+     *
+     * @param array $worlds
+     * @return void
+     */
+    public static function update2(array $worlds)
+    {
+        $rows = count($worlds);
+
+        if (!isset(self::$update[$rows])) {
+            $sql = 'UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES '
+                . implode(', ', array_fill(0, $rows, '(?::INTEGER, ?::INTEGER)')) .
+                ' ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id';
+
+            self::$update[$rows] = self::$instance->prepare($sql);
+        }
+
+        $val = [];
+        foreach ($worlds as $world) {
+            $val[] = $world['id'];
+            $val[] = $world['randomNumber'];
+            //$update->bindParam(++$i, $world['id'], PDO::PARAM_INT);
+        }
+
+        self::$update[$rows]->execute($val);
+    }
+}

+ 16 - 0
frameworks/PHP/slim/deploy/conf/cli-php.ini

@@ -0,0 +1,16 @@
+#zend_extension=opcache.so
+opcache.enable=1
+opcache.enable_cli=1
+opcache.validate_timestamps=0
+opcache.save_comments=0
+opcache.enable_file_override=1
+opcache.huge_code_pages=1
+
+mysqlnd.collect_statistics = Off
+
+memory_limit = 512M
+
+opcache.jit_buffer_size = 128M
+opcache.jit = tracing
+
+disable_functions=header,header_remove,http_response_code,setcookie,session_create_id,session_id,session_name,session_save_path,session_status,session_start,session_write_close,session_regenerate_id,set_time_limit

+ 65 - 51
frameworks/PHP/slim/index.php

@@ -3,55 +3,61 @@ error_reporting(-1);
 
 require_once __DIR__.'/vendor/autoload.php';
 
-$app = new Slim\App(array(
-    'db' => function ($c) {
-        $pdo = new PDO('mysql:host=tfb-database;dbname=hello_world;charset=utf8', 'benchmarkdbuser', 'benchmarkdbpass', array(
-            PDO::ATTR_PERSISTENT => true,
-        ));
-        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-        $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
-
-        return $pdo;
-    },
-
-    'view' => function ($c) {
-        return new Slim\Views\PhpRenderer("templates/");
-    }
-));
+use DI\Container;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Factory\AppFactory;
+use Slim\Views\PhpRenderer;
+
+//global $app; // workerman
+
+$container = new Container();
+
+$container->set('db', new PDO(
+                'mysql:host=tfb-database;dbname=hello_world;charset=utf8', 
+            'benchmarkdbuser', 
+            'benchmarkdbpass',
+            [
+                    PDO::ATTR_PERSISTENT          => true,
+                    PDO::ATTR_DEFAULT_FETCH_MODE  => PDO::FETCH_ASSOC,
+                    PDO::ATTR_ERRMODE             => PDO::ERRMODE_EXCEPTION,
+                    //PDO::ATTR_EMULATE_PREPARES    => false, // workerman
+            ]));
+
+$container->set('view', new PhpRenderer('templates'));
+
+AppFactory::setContainer($container);
+$app = AppFactory::create();
+
 
 // Test 1: Plaintext
-$app->get('/plaintext', function ($request, $response) {
-    return $response
+$app->get('/plaintext', fn(Request $request, Response $response) =>
+    $response
         ->write('Hello, World!')
         ->withHeader('Content-Type', 'text/plain')
-        ;
-});
+);
 
 // Test 2: JSON serialization
-$app->get('/json', function ($request, $response) {
-    return $response
-        ->withJson(array('message' => 'Hello, World!'))
-        ->withHeader('Content-Type', 'application/json') // fixes utf-8 warning
-        ;
-});
+$app->get('/json', fn(Request $request, Response $response) =>
+    $response
+        ->withJson(['message' => 'Hello, World!'])
+);
 
 // Test 3: Single database query
-$app->get('/db', function ($request, $response) {
-    $sth = $this->db->prepare('SELECT * FROM World WHERE id = ?');
-    $sth->execute(array(mt_rand(1, 10000)));
+$app->get('/db', function (Request $request, Response $response) {
+    $sth = $this->get('db')->prepare('SELECT * FROM World WHERE id = ?');
+    $sth->execute([mt_rand(1, 10000)]);
     $world = $sth->fetch();
     # Cast fields to int so they don't get wrapped with quotes
     $world['id'] = (int) $world['id'];
     $world['randomNumber'] = (int) $world['randomNumber'];
 
     return $response
-        ->withJson($world)
-        ->withHeader('Content-Type', 'application/json') // fixes utf-8 warning
-        ;
+        ->withJson($world);
 });
 
 // Test 4: Multiple database queries
-$app->get('/dbs', function ($request, $response) {
+$app->get('/dbs', function (Request $request, Response $response) {
     $queries = $request->getParam('queries');
     if (is_numeric($queries)) {
         $queries = max(1, min($queries, 500));
@@ -59,10 +65,10 @@ $app->get('/dbs', function ($request, $response) {
         $queries = 1;
     }
 
-    $sth = $this->db->prepare('SELECT * FROM World WHERE id = ?');
-    $worlds = array();
+    $sth = $this->get('db')->prepare('SELECT * FROM World WHERE id = ?');
+    $worlds = [];
     for ($i = 0; $i < $queries; ++$i) {
-        $sth->execute(array(mt_rand(1, 10000)));
+        $sth->execute([mt_rand(1, 10000)]);
         $world = $sth->fetch();
         # Cast fields to int so they don't get wrapped with quotes
         $world['id'] = (int) $world['id'];
@@ -71,13 +77,11 @@ $app->get('/dbs', function ($request, $response) {
     }
 
     return $response
-        ->withJson($worlds)
-        ->withHeader('Content-Type', 'application/json') // fixes utf-8 warning
-        ;
+        ->withJson($worlds);
 });
 
 // Test 5: Updates
-$app->get('/updates', function ($request, $response) {
+$app->get('/updates', function (Request $request, Response $response) {
     $queries = $request->getParam('queries');
     if (is_numeric($queries)) {
         $queries = max(1, min($queries, 500));
@@ -85,38 +89,48 @@ $app->get('/updates', function ($request, $response) {
         $queries = 1;
     }
 
-    $sth = $this->db->prepare('SELECT * FROM World WHERE id = ?');
-    $updateSth = $this->db->prepare('UPDATE World SET randomNumber = ? WHERE id = ?');
+    $sth = $this->get('db')->prepare('SELECT * FROM World WHERE id = ?');
+    $updateSth = $this->get('db')->prepare('UPDATE World SET randomNumber = ? WHERE id = ?');
 
-    $worlds = array();
+    $worlds = [];
     for ($i = 0; $i < $queries; ++$i) {
         $id = mt_rand(1, 10000);
         $random_number = mt_rand(1, 10000);
-        $sth->execute(array($id));
+        $sth->execute([$id]);
         $world = $sth->fetch();
         # Cast fields to int so they don't get wrapped with quotes
         $world['id'] = (int) $world['id'];
         $world['randomNumber'] = $random_number;
 
-        $updateSth->execute(array($world['randomNumber'], $world['id']));
+        $updateSth->execute([$world['randomNumber'], $world['id']]);
 
         $worlds[] = $world;
     }
 
     return $response
-        ->withJson($worlds)
-        ->withHeader('Content-Type', 'application/json') // fixes utf-8 warning
-        ;
+        ->withJson($worlds);
 });
 
 // Test 6: Fortunes
-$app->get('/fortunes', function ($request, $response) {
-    $fortunes = $this->db->query('SELECT * FROM Fortune')->fetchAll(PDO::FETCH_KEY_PAIR);
+$app->get('/fortunes', function (Request $request, Response $response) {
+    $fortunes = $this->get('db')->query('SELECT * FROM Fortune')->fetchAll(PDO::FETCH_KEY_PAIR);
 
     $fortunes[0] = 'Additional fortune added at request time.';
     asort($fortunes);
 
-    return $this->view->render($response, "fortunes.php", ["fortunes" => $fortunes]);
+    return $this->get('view')->render($response, 'fortunes.php', ['fortunes' => $fortunes]);
 });
 
-$app->run();
+$app->run(); // comented with workerman
+
+// used by Workerman
+function run(): string
+{
+    global $app;
+    ob_start();
+
+    $app->run();
+    header(HeaderDate::$date); // To pass the bench, nginx auto add it
+
+    return ob_get_clean();
+}

+ 46 - 0
frameworks/PHP/slim/server.php

@@ -0,0 +1,46 @@
+<?php
+require_once __DIR__ . '/vendor/autoload.php';
+
+
+use Adapterman\Adapterman;
+use Workerman\Worker;
+use Workerman\Lib\Timer;
+
+Adapterman::init();
+
+$http_worker                = new Worker('http://0.0.0.0:8080');
+$http_worker->count         = (int) shell_exec('nproc') * 4;
+$http_worker->name          = 'AdapterMan-Slim';
+
+$http_worker->onWorkerStart = function () {
+    HeaderDate::init();
+    //init();
+    require __DIR__.'/index.php';
+};
+
+$http_worker->onMessage = static function ($connection, $request) {
+
+    $connection->send(run());
+};
+
+Worker::runAll();
+
+class HeaderDate
+{
+    const NAME = 'Date: ';
+    
+    /**
+     * Date header
+     *
+     * @var string
+     */
+    public static $date;
+
+    public static function init(): void
+    {
+        self::$date = self::NAME . gmdate('D, d M Y H:i:s').' GMT';
+        Timer::add(1, static function() {
+            self::$date = self::NAME . gmdate('D, d M Y H:i:s').' GMT';
+        });
+    }
+}

+ 31 - 0
frameworks/PHP/slim/slim-workerman-pgsql.dockerfile

@@ -0,0 +1,31 @@
+FROM ubuntu:22.04
+
+ARG DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get update -yqq && apt-get install -yqq software-properties-common > /dev/null
+RUN LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php > /dev/null && \
+    apt-get update -yqq > /dev/null && apt-get upgrade -yqq > /dev/null
+
+RUN apt-get install -yqq git \
+    php8.1-cli php8.1-pgsql php8.1-mbstring php8.1-xml php8.1-curl > /dev/null
+
+COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
+
+RUN apt-get install -y php-pear php8.1-dev libevent-dev > /dev/null
+RUN pecl install event-3.0.8 > /dev/null && echo "extension=event.so" > /etc/php/8.1/cli/conf.d/event.ini
+
+COPY deploy/conf/cli-php.ini /etc/php/8.1/cli/php.ini
+
+ADD ./ /slim
+WORKDIR /slim
+
+EXPOSE 8080
+
+RUN composer install --optimize-autoloader --classmap-authoritative --no-dev --quiet
+RUN composer require joanhey/adapterman
+
+RUN sed -i 's|/index.php|/start.php|g' server.php
+
+RUN chmod -R 777 /slim
+
+CMD php server.php start

+ 34 - 0
frameworks/PHP/slim/slim-workerman.dockerfile

@@ -0,0 +1,34 @@
+FROM ubuntu:22.04
+
+ARG DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get update -yqq && apt-get install -yqq software-properties-common > /dev/null
+RUN LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php > /dev/null && \
+    apt-get update -yqq > /dev/null && apt-get upgrade -yqq > /dev/null
+
+RUN apt-get install -yqq git \
+    php8.1-cli php8.1-mysql php8.1-mbstring php8.1-xml php8.1-curl > /dev/null
+
+COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
+
+RUN apt-get install -y php-pear php8.1-dev libevent-dev > /dev/null
+RUN pecl install event-3.0.8 > /dev/null && echo "extension=event.so" > /etc/php/8.1/cli/conf.d/event.ini
+
+COPY deploy/conf/cli-php.ini /etc/php/8.1/cli/php.ini
+
+ADD ./ /slim
+WORKDIR /slim
+
+EXPOSE 8080
+
+RUN composer install --optimize-autoloader --classmap-authoritative --no-dev --quiet
+RUN composer require joanhey/adapterman
+
+
+RUN sed -i 's|$app->run(); //| //$app->run(); //|g' index.php
+RUN sed -i 's|//global $app;|global $app;|g' index.php
+RUN sed -i 's|//PDO::ATTR_EMULATE_PREPARES|PDO::ATTR_EMULATE_PREPARES|g' index.php
+
+RUN chmod -R 777 /slim
+
+CMD php server.php start

+ 8 - 6
frameworks/PHP/slim/slim.dockerfile

@@ -3,18 +3,20 @@ FROM ubuntu:22.04
 ARG DEBIAN_FRONTEND=noninteractive
 
 RUN apt-get update -yqq && apt-get install -yqq software-properties-common > /dev/null
-RUN LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
-RUN apt-get update -yqq > /dev/null && \
-    apt-get install -yqq nginx git unzip php8.0 php8.0-common php8.0-cli php8.0-fpm php8.0-mysql php8.0-xml > /dev/null
+RUN LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php > /dev/null && \
+    apt-get update -yqq > /dev/null && apt-get upgrade -yqq > /dev/null
+
+RUN apt-get install -yqq nginx git unzip \
+    php8.1 php8.1-common php8.1-cli php8.1-fpm php8.1-mysql php8.1-xml php8.1-curl > /dev/null
 
 COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
 
-COPY deploy/conf/* /etc/php/8.0/fpm/
+COPY deploy/conf/* /etc/php/8.1/fpm/
 
 ADD ./ /slim
 WORKDIR /slim
 
-RUN if [ $(nproc) = 2 ]; then sed -i "s|pm.max_children = 1024|pm.max_children = 512|g" /etc/php/8.0/fpm/php-fpm.conf ; fi;
+RUN if [ $(nproc) = 2 ]; then sed -i "s|pm.max_children = 1024|pm.max_children = 512|g" /etc/php/8.1/fpm/php-fpm.conf ; fi;
 
 RUN composer install --optimize-autoloader --classmap-authoritative --no-dev --quiet
 
@@ -22,5 +24,5 @@ RUN chmod -R 777 /slim
 
 EXPOSE 8080
 
-CMD service php8.0-fpm start && \
+CMD service php8.1-fpm start && \
     nginx -c /slim/deploy/nginx.conf

+ 117 - 0
frameworks/PHP/slim/start.php

@@ -0,0 +1,117 @@
+<?php
+error_reporting(-1);
+
+require_once __DIR__.'/vendor/autoload.php';
+
+use DI\Container;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Factory\AppFactory;
+use Slim\Views\PhpRenderer;
+use Db\Raw;
+
+global $app; // workerman
+
+Raw::init();
+
+$container = new Container();
+
+$container->set('view', new PhpRenderer('templates'));
+
+AppFactory::setContainer($container);
+$app = AppFactory::create();
+
+
+// Test 1: Plaintext
+$app->get('/plaintext', fn(Request $request, Response $response) =>
+    $response
+        ->write('Hello, World!')
+        ->withHeader('Content-Type', 'text/plain')
+);
+
+// Test 2: JSON serialization
+$app->get('/json', fn(Request $request, Response $response) =>
+    $response
+        ->withJson(['message' => 'Hello, World!'])
+);
+
+// Test 3: Single database query
+$app->get('/db', function (Request $request, Response $response) {
+    
+    Raw::$random->execute([mt_rand(1, 10000)]);
+
+    return $response
+        ->withJson(Raw::$random->fetch());
+});
+
+// Test 4: Multiple database queries
+$app->get('/dbs', function (Request $request, Response $response) {
+    $queries = $request->getParam('queries');
+    if (is_numeric($queries)) {
+        $queries = max(1, min($queries, 500));
+    } else {
+        $queries = 1;
+    }
+
+    $sth = Raw::$random;
+    $worlds = [];
+    for ($i = 0; $i < $queries; ++$i) {
+        $sth->execute([mt_rand(1, 10000)]);
+        $worlds[] = $sth->fetch();
+    }
+
+    return $response
+        ->withJson($worlds);
+});
+
+// Test 5: Updates
+$app->get('/updates', function (Request $request, Response $response) {
+    $queries = $request->getParam('queries');
+    if (is_numeric($queries)) {
+        $queries = max(1, min($queries, 500));
+    } else {
+        $queries = 1;
+    }
+
+    $sth = Raw::$random;
+    //$updateSth = Raw::update();
+
+    $worlds = [];
+    for ($i = 0; $i < $queries; ++$i) {
+        $sth->execute([mt_rand(1, 10000)]);
+        $world = $sth->fetch();
+        $world['randomNumber'] = mt_rand(1, 10000);
+
+        $worlds[] = $world;
+    }
+
+    Raw::update($worlds);
+
+    return $response
+        ->withJson($worlds);
+});
+
+// Test 6: Fortunes
+$app->get('/fortunes', function (Request $request, Response $response) {
+    Raw::$fortune->execute();
+    $fortunes = Raw::$fortune->fetchAll(PDO::FETCH_KEY_PAIR);
+
+    $fortunes[0] = 'Additional fortune added at request time.';
+    asort($fortunes);
+
+    return $this->get('view')->render($response, 'fortunes.php', ['fortunes' => $fortunes]);
+});
+
+//$app->run(); // comented with workerman
+
+// used by Workerman
+function run(): string
+{
+    global $app;
+    ob_start();
+
+    $app->run();
+    header(HeaderDate::$date); // To pass the bench, nginx auto add it
+
+    return ob_get_clean();
+}

+ 4 - 4
frameworks/PHP/slim/templates/fortunes.php

@@ -7,12 +7,12 @@
             <th>id</th>
             <th>message</th>
         </tr>
-        <?php foreach($data['fortunes'] as $id =>$fortune){ ?>
+        <?php foreach($data['fortunes'] as $id =>$fortune) : ?>
             <tr>
-                <td><?php echo $id; ?></td>
-                <td><?php echo htmlspecialchars($fortune); ?></td>
+                <td><?= $id ?></td>
+                <td><?= htmlspecialchars($fortune) ?></td>
             </tr>
-        <?php } ?>
+        <?php endforeach ?>
     </table>
   </body>
 </html>