-
DONE: Add Middleware slide code to php-iii-demos repo
-
For the PHP III demos, get the
index.php
working correctly!
For remaining labs:
- PHP Async:
- Examples are in the php-iii-demos repo
- For the last course module labs
- See lab notes below
For Fri 02 Feb 2024
- Lab: Adding Middleware
- REST Service Development Lab
- Laminas API Tools
- Do all labs under this section
For Wed 31 Jan 2024
- Lab: Docker Image Build
- Do all the labs in this section
- Lab: Commit the Image
- Lab: Docker Compose Labs
- Do all the labs in this section
For Mon 29 Jan 2024
- Lab: OpCache and JIT
- Lab: Existing Extension
- Lab: FFI
- Lab: New Extension [Optional]
- Lab: Custom PHP
- Lab: Customized PHP Prerequisites
- Lab: Installing Library Dependencies
- Lab: Installing Customized PHP
For Fri 26 Jan 2024
- Lab: Built-in Web Server
-
Q: Example using
stream_context_create()
andfile_get_contents()
? -
A: See: https://github.com/dbierer/classic_php_examples/blob/master/web/rest_api_call_us_weather_svc.php
-
Q: What is the RedHat equivalent of Debian
update-alternatives
? -
A: In the RedHat world it's just
alternatives
. -
Q: Do you have an Apcu example of the full-page cache example shown i the slides?
-
A: Here it is:
<?php
// First check for cache
if (apcu_exists('filecache')) {
echo apcu_fetch('filecache');
exit;
}
// No cache so we need to produce the page
$view = new stdClass();
$view->label = 'Current Transaction Listing';
$handle = fopen('bitcoin.csv', 'r');
while($row = fgetcsv($handle)) $view->data[] = $row;
fclose($handle);
// Render the view
ob_start();
require 'layout.phtml'; // includes logic that renders $view
// Save to cache (assumes write privileges)
$output = ob_get_clean();
apcu_store('filecache', $output, 3600);
// the page
echo $output;
- Q: Where is the table schema for Stream Wrapper example?
- A: Look here:
/home/vagrant/Zend/workspaces/DefaultWorkspace/php3/src/ModAdvancedTechniques/IO/SQL/data.sql
CREATE TABLE `data` (
`id` int(11) NOT NULL,
`data` varchar(255) NOT NULL,
`date` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
- Q: Do you have an
SplHeap
example that has the priority as a key instead of what's shown in the slides? - A: Here's the rewritten version:
<?php
$list =[
1 => 'Comm check',
4 => 'Fuel load check',
3 => 'Batteries at max check',
9 => 'Space suit check',
6 => 'Landing struts retracted check',
];
$sequencer = new class() extends SplHeap {
// Set the sequence
public function compare($arr1, $arr2)
{
// Do the comparison using the spaceship operator
return key($arr2) <=> key($arr1);
}
};
foreach($list as $priority => $item) {
$sequencer->insert([$priority => $item]);
}
$sequencer->top();
while($sequencer->valid()) {
printf("%02d : %s\n", key($sequencer->current()), current($sequencer->current()));
$sequencer->next();
}
Follow these instructions:
- https://opensource.unlikelysource.com/expanded_vm_instructions.pdf
- Upgrade/update commands from the PDF:
sudo apt -y update
sudo apt -f -y install && sudo apt -y full-upgrade
sudo apt autoremove
- Apache reconfig from the PDF:
sudo apt-add-repository ppa:ondrej/apache2
sudo apt install libapache2-mod-php8.3
sudo a2dismod php8.0
sudo a2enmod php8.3
sudo systemctl restart apache2
Freeing up disk space in the VM:
snap remove postman
snap remove phpstorm
snap remove mysql-workbench-community
Follow these steps:
- Download your preferred version of Adminer from
https://www.adminer.org/#download
cd
wget https://github.com/vrana/adminer/releases/download/v4.8.1/adminer-4.8.1-mysql-en.php
- Move to
/var/www/html
sudo mv adminer-4.8.1-mysql-en.php /var/www/html/adminer.php
- Access from the browser:
http://localhost/adminer.php
Object Relational Mapping
- https://www.doctrine-project.org/ Example of Active Record (database design)
- https://github.com/dbierer/classic_php_examples/blob/master/db/db_active_record_example.php Relative DateTime Formats
- https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative Example of a working "Event" system
- https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html
- Lab Code:
- Clone this repo: https://github.com/dbierer/php-iii-demos.git
- Source code is located here:
/home/vagrant/Zend/workspaces/DefaultWorkspace
- Lab: OpCache and JIT
- The code for the Mandelbrot needs
__construct()
- The code for the Mandelbrot needs
- Lab: Adding Middleware
- Take the code from the slides
- Add a middleware request handler that implements an update (HTTP "PATCH")
- Lab: New Extension
- Lab needs additional work
- If you follow the instructions here exactly, "test1()" works, but "test2()" does not
- Lab: Docker Compose Labs
- Have a look at the article on Orchestration: https://www.zend.com/blog/what-is-cloud-orchestration
- CLI utility to reset JIT:
- https://github.com/dbierer/PHP-8-Programming-Tips-Tricks-and-Best-Practices/blob/main/ch10/php8_jit_reset.php
- Run the demo as a web page and adjust params under
/etc/php/PHP_VER/apache2/php.ini
- Don't forget to renable JIT in
/etc/php/PHP_VER/apache2/conf.d/10-opcache.ini
- Example timinig results:
w/out opcache: ~2.7
with opcache: ~2.5
with JIT function: ~0.75
with JIT tracking: ~0.41
- Swoole Lab
- Just install the default package:
sudo apt install php8.3-swoole
- Change "8.3" to the current PHP version
- From
php-iii-demos
run composer install:
php composer.phar self-update
php composer.phar install --ignore-platform-reqs
-
Run these three program under
src
and compare the time:normal.php
swoole.php
react.php
-
API Tools Lab
- Don't forget to add
--ignore-platform-reqs
when installing Laminas API Tools
- Don't forget to add
composer --ignore-platform-reqs create-project laminas-api-tools/api-tools-skeleton
- For any interactive prompts select the suggested default value
- If you install it in another directory other than the one in the lab, you can do this:
cd path/to/api/tools
php -S 0.0.0.0:8080 -t public public/index.php
- Apache JMeter
- Download binary from: https://jmeter.apache.org/download_jmeter.cgi
- Extract to
/home/vagrant/jmeter
- From a terminal window run the shell script:
/home/vagrant/jmeter/bin/jmeter.sh
- Clone from github
- Switch to branch target version of PHP (e.g. 7.4.11)
git checkout php-PHP_VER
- Follow the instructions
- Be sure to install the pre-requisites!
- Suggested
./configure
options (place this all on one line):
./configure \
--enable-cli \
--enable-filter \
--with-openssl \
--with-zlib \
--with-curl \
--enable-pdo \
--with-libxml \
--with-iconv \
--enable-cgi \
--enable-session \
--with-pdo-mysql \
--enable-phar \
--with-pdo-sqlite \
--with-pcre-jit \
--with-zip \
--enable-ctype \
--enable-gd \
--enable-bcmath \
--enable-sockets \
--with-bz2 \
--enable-exif \
--enable-intl \
--with-gettext \
--enable-opcache \
--enable-fileinfo \
--with-readline \
--with-sodium
checking for BZip2 in default path... not found
configure: error: Please reinstall the BZip2 distribution
- https://unix.stackexchange.com/questions/658758/php-build-error-please-reinstall-bzip2-distribution
sudo apt install -y libbz2-dev
configure: error: Package requirements (libcurl >= 7.29.0) were not met:
No package 'libcurl' found
sudo apt install -y libcurl4-openssl-dev
configure: error: Please reinstall readline - I cannot find readline.h
- https://stackoverflow.com/questions/35879203/linux-php-7-configure-error-please-reinstall-readline-i-cannot-find-readline
sudo apt install -y libreadline-dev
configure: error: Package requirements (libsodium >= 1.0.8) were not met:
No package 'libsodium' found
sudo apt install -y libsodium-dev
configure: error: Package requirements (zlib) were not met:
No package 'zlib' found
- NOTE: this error actually comes from installing the
GD
extension - See: https://www.php.net/manual/en/image.installation.php
- As of PHP 7.4.0,
--with-png-dir
and--with-zlib-dir
have been removed.libpng
andzlib
are required.
- As of PHP 7.4.0,
- See: https://askubuntu.com/questions/508934/how-to-install-libpng-and-zlib
sudo apt install -y libpng-dev zlib1g-dev
configure: error: Package requirements (libzip >= 0.11 libzip != 1.3.1 libzip != 1.7.0) were not met:
No package 'libzip' found
- https://stackoverflow.com/questions/45775877/configure-error-please-reinstall-the-libzip-distribution
sudo apt install -y libzip-dev
Final Solution:
sudo apt install -y libbz2-dev libpng-dev zlib1g-dev libsodium-dev \
libreadline-dev libcurl4-openssl-dev libbz2-dev
To switch versions use update-alternatives --config php
(see below for more info)
Instructions for Jenkins using the official Docker image
- General usage: https://github.com/jenkinsci/docker/blob/master/README.md
- Use this image:
jenkins/jenkins:alpine3.18-jdk21
- To run the image:
- Use this image:
mkdir jenkins_home
docker run -p 8080:8080 -p 50000:50000 --restart=on-failure -v jenkins_home:`pwd`/jenkins_home jenkins/jenkins:alpine3.18-jdk21
- Look for a message about the admin password
- S/be located here:
/var/jenkins_home/secrets/initialAdminPassword
- S/be located here:
- To access, from a browser:
http://localhost:8080
- Installed the "suggested" plugins + those on the list for the lab
Supplementary date-related functions:
Full DateTime::format()
codes:
- https://www.php.net/manual/en/datetime.format.php
Getting differences between dates, use
diff()
<?php
$today = new DateTime('now');
$past = new DateTime('1970-01-01');
$future = new DateTime('2024-12-01');
$diff[] = $today->diff($past); // $diff->invert === 1
$diff[] = $today->diff($future); // $diff->invert === 0
var_dump($diff);
// actual output for today (2023-11-28)
/*
* array(2) {
[0]=>
object(DateInterval)#4 (10) {
["y"]=>
int(53)
["m"]=>
int(10)
["d"]=>
int(27)
["h"]=>
int(2)
["i"]=>
int(15)
["s"]=>
int(15)
["f"]=>
float(0.395718)
["invert"]=>
int(1)
["days"]=>
int(19689)
["from_string"]=>
bool(false)
}
[1]=>
object(DateInterval)#5 (10) {
["y"]=>
int(1)
["m"]=>
int(0)
["d"]=>
int(2)
["h"]=>
int(21)
["i"]=>
int(44)
["s"]=>
int(44)
["f"]=>
float(0.604282)
["invert"]=>
int(0)
["days"]=>
int(368)
["from_string"]=>
bool(false)
}
}
*/
Adding a date, create a DateInterval
instance
<?php
$date = new DateTime('now');
$date->add(new DateInterval('P92D'));
echo $date->format('l, j M Y');
// example output: Wednesday, 3 Mar 2023
Relative time formats
<?php
$date = new DateTime('third thursday of next month');
echo $date->format('l, j M Y');
echo PHP_EOL;
$date = new DateTime('last day of last month');
echo $date->format('l, j M Y');
echo PHP_EOL;
Example that demonstrates memory savings using a Generator
<?php
$arr = range(1,100000);
function test(array $arr)
{
$result = [];
foreach ($arr as $item)
$result[] = $item * 1.08;
return $result;
}
foreach (test($arr) as $item) echo $item . ' ';
echo 'Peak Memory: ' . memory_get_peak_usage(); // Peak Memory: 4,596,064
function test2(array $arr)
{
foreach ($arr as $item)
yield $item * 1.08;
}
foreach (test2($arr) as $item) echo $item . ' ';
echo 'Peak Memory: ' . memory_get_peak_usage(); // Peak Memory: 2,494,824
Does not implement the Countable
interface:
function test2(array $arr)
{
foreach ($arr as $item)
yield $item * 1.08;
}
$result = test2($arr);
echo count($result);
// PHP Fatal error: Uncaught TypeError: count(): Argument #1 ($value) must be of type Countable|array, Generator given in /home/vagrant/Zend/workspaces/DefaultWorkspace/sandbox/public/test.php:20
Extracting a return value from a Generator
- The iteration must be complete
- Use
getReturn()
to extract the return value
<?php
$arr = range(1,100000);
function test2(array $arr)
{
$sum = 0;
foreach ($arr as $item) {
$new = $item * 1.08;
yield $new;
$sum += $new;
}
return $sum;
}
$gen = test2($arr);
foreach ($gen as $item) echo $item . ' ';
echo PHP_EOL;
echo $gen->getReturn();
Make an object "iterable" by implementing IteratorAggregate
:
<?php
class Test implements IteratorAggregate
{
public function __construct(public string $first, public string $last, public string $role) {}
public function getIterator()
{
return new ArrayIterator(get_object_vars($this));
}
}
$test = new Test('Fred', 'Flintstone', 'Caveman');
foreach ($test as $item) echo $item . PHP_EOL;
var_dump(iterator_to_array($test));
Example shown on the slides with a slight modification
<?php
// change as needed
define('REGEX', '!.*?spl.*?\.php!i');
// starting path for search
$path = realpath(__DIR__);
// set up directory iteration
$dirIterator = new RecursiveDirectoryIterator($path);
$recIterator = new RecursiveIteratorIterator($dirIterator);
// define filter using an anonymous class
$filtIterator = new class ($recIterator) extends FilterIterator {
public function accept()
{
// $this->key() : returns the filename (full path)
// $this->current(): returns an SplFileInfo instance
return preg_match(REGEX, $this->current()->getBasename());
}
};
// display results
foreach ($filtIterator as $name => $obj) echo $name . "\n";
Example where the return value is an anon class with different methods to render its data
<?php
class Test
{
public function getObject(array $arr)
{
return new class ($arr) {
public $arr = [];
public function __construct(array $arr)
{
$this->arr = $arr;
}
public function asHtml()
{
$html = '<ul>' . PHP_EOL;
foreach ($this->arr as $item) $html .= '<li>' . $item . '</li>' . PHP_EOL;
$html .= '</ul>' . PHP_EOL;
return $html;
}
public function asJson()
{
return json_encode($this->arr, JSON_PRETTY_PRINT);
}
};
}
}
$arr = ['AAA','BBB','CCC','DDD'];
$obj = (new Test())->getObject($arr);
echo $obj->asHtml();
echo $obj->asJson();
var_dump($obj->arr);
var_dump($obj);
Example from the slide "Event Listener" using __invoke()
to make it callable:
// An anonymous event class listener example
$listener = new class {
public function __invoke(Event $e)
{
echo "The big event \" { $e->getName ()} \" is happening!" ;
}
};
Potential problem: how does the user (i.e. another developer) know that this functionality is available
- Solution: have the anonymous class implement an interface:
<?php
interface HtmlJson
{
public function asHtml();
public function asJson();
}
class Test
{
public function getObject(array $arr)
{
return new class ($arr) implements HtmlJson {
public $arr = [];
public function __construct(array $arr)
{
$this->arr = $arr;
}
public function asHtml()
{
$html = '<ul>' . PHP_EOL;
foreach ($this->arr as $item) $html .= '<li>' . $item . '</li>' . PHP_EOL;
$html .= '</ul>' . PHP_EOL;
return $html;
}
public function asJson()
{
return json_encode($this->arr, JSON_PRETTY_PRINT);
}
};
}
}
$arr = ['AAA','BBB','CCC','DDD'];
$obj = (new Test())->getObject($arr);
echo $obj->asHtml();
echo $obj->asJson();
var_dump($obj->arr);
var_dump($obj);
Traversable
connects the old approach (Iterator
) with a newer approach (IteratorAggregate
)
<?php
class Test implements IteratorAggregate
{
protected $name = 'Doug';
protected $country = 'Thailand';
protected $language = 'EN';
public function getIterator()
{
return new ArrayIterator(get_object_vars($this));
}
}
$test = new Test();
foreach($test as $key => $value) echo $key . ':' . $value . PHP_EOL;
Yet another example:
<?php
class User implements IteratorAggregate
{
public $first = 'Fred';
public $last = 'Flintstone';
public $role = 'Caveman';
public $date = NULL;
public function __construct()
{
$this->date = new DateTime();
}
public function getIterator()
{
$list = get_object_vars($this);
$list['date'] = $this->date->format('l, j M Y');
return new ArrayIterator($list);
}
}
$user = new User();
function looper(Traversable $trav)
{
foreach ($trav as $key => $val) echo "$key\t$val\n";
}
looper($user);
In this example, note that if we uncomment line 8, the legacy code still works
- The reason is because
ArrayObject
implementsArrayAccess
- We can also use an anonymous class to extend
ArrayObject
and add new functionality
<?php
$arr = [
'first' => 'Fred',
'last' => 'Flintstone',
'amount' => 99.99,
];
// if you uncomment the next line, $arr becomes an object, but the remaining code works OK
$arr = new class($arr) extends ArrayObject
{
public function getJson()
{
return json_encode($this->getArrayCopy(), JSON_PRETTY_PRINT);
}
};
// some other code
$purch = (float) ($_GET['purch'] ?? 1.11);
$arr['amount'] += $purch;
$arr['tax'] = $arr['amount'] * 0.07;
// some other code
// final output:
echo '<table>';
foreach ($arr as $key => $val)
echo '<tr><th>' . $key . '</th><td>' . $val . '</td></tr>' . PHP_EOL;
echo '</table>';
echo PHP_EOL;
echo $arr->getJson();
Using an interface as a data type
<?php
interface AddInterface
{
public function add(int $a, int $b) : int;
}
class Test implements AddInterface
{
public function add(int $a, int $b) : int
{
return $a + $b;
}
}
class Baby extends Test
{
public function sub(int $a, int $b) : int
{
return $a - $b;
}
}
class Something implements AddInterface
{
public function add(int $a, int $b) : int
{
return $a + $b;
}
}
function whatever(AddInterface $obj, int $x, int $y)
{
echo "The sum of $x and $y is: ";
echo $obj->add($x, $y);
echo PHP_EOL;
}
// all three instances work because they are associated with AddInterface at some level
$test = new Test();
whatever($test, 2, 2);
$baby = new Baby();
whatever($baby, 2, 2);
$some = new Something();
whatever($some, 2, 2);
Anytime you implement __toString()
<?php
class Test
{
protected $name = 'Doug';
protected $country = 'Thailand';
protected $language = 'EN';
public function __toString()
{
return var_export(get_object_vars($this), TRUE);
}
}
$test = new Test();
echo $test;
echo PHP_EOL;
$reflect = new ReflectionObject($test);
echo $reflect;
echo PHP_EOL;
// output
/*
* Object of class [ <user> class Test implements Stringable ] {
@@ C:\Users\azure\Desktop\test.php 2-11
- Constants [0] {
}
...
*/
It's treated just like an array
<?php
$user = [
'user' => 'joe',
'email' => '[email protected]',
'address' => '123 Main Street',
'city' => 'Utrecht',
'country' => 'NL',
];
$user = new ArrayObject($user);
$user['status'] = 'OK';
echo 'Name :' . $user['user'] . PHP_EOL;
echo 'Email :' . $user['email'] . PHP_EOL;
echo 'City :' . $user['city'] . PHP_EOL;
echo 'Status:' . $user['status'] . PHP_EOL;
ArrayIterator
example
<?php
$data = [
'F' => 666,
'A' => 111,
'E' => 555,
'C' => 333,
'B' => 222,
'D' => 444,
];
// here's the traditional way to use a while() with an array:
asort($data);
$pos = 0;
$count = count($data);
while ($pos++ < $count) {
echo key($data) . ':' . current($data) . PHP_EOL;
next($data);
}
// same thing but using ArrayIterator:
$it = new ArrayIterator($data);
$it->asort();
while ($it->valid()) {
echo $it->key() . ':' . $it->current() . PHP_EOL;
$it->next();
}
Example of linked list:
<?php
$base = [
'A' => 111,
'B' => 222,
'C' => 333,
'D' => 444,
'E' => 555,
'F' => 666,
];
$link = ['F','E','D','C','B','A'];
foreach ($link as $key)
echo $base[$key] . PHP_EOL;
Example of doubly linked list (using just arrays)
<?php
$base = [
'A' => 111,
'B' => 222,
'C' => 333,
'D' => 444,
'E' => 555,
'F' => 666,
];
$reverse = ['F','E','D','C','B','A'];
$forward = ['A','B','C','D','E','F'];
function showBase(array $link, array $base)
{
foreach ($link as $key)
echo $base[$key] . PHP_EOL;
}
echo showBase($forward, $base);
echo showBase($reverse, $base);
Example of a linked list
<?php
$arr = ['A' => 111, 'B' => 222, 'C' => 333, 'D' => 444, 'E' => 555, 'F' => 666];
$rev = range('F','A');
$fwd = range('A','F');
foreach ($fwd as $key) echo $arr[$key] . PHP_EOL;;
foreach ($rev as $key) echo $arr[$key] . PHP_EOL;;
Example of doubly linked list (using SplDoublyLinkedList
)
<?php
$obj = new SplDoublyLinkedList();
$obj[] = 111;
$obj[] = 222;
$obj[] = 333;
$obj[] = 444;
$obj[] = 555;
$obj[] = 666;
function showBaseObj(object $obj)
{
foreach ($obj as $value)
echo "$value\n";
}
echo showBaseObj($obj);
$obj->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO );
echo showBaseObj($obj);
Another example building the SplDoublyLinkList
manually
<?php
$val = 100;
$dbl = new SplDoublyLinkedList();
for ($x = 0; $x < 6; $x++) {
$dbl->add($x, $val);
$val += 100;
}
foreach ($dbl as $key => $val) {
echo $key . ':' . $val;
echo PHP_EOL;
}
$dbl->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO);
foreach ($dbl as $key => $val) {
echo $key . ':' . $val;
echo PHP_EOL;
}
Recurse through an entire directory structure
<?php
$path = __DIR__ . '/../../orderapp';
$dir = new RecursiveDirectoryIterator($path);
$rec = new RecursiveIteratorIterator($dir);
foreach ($rec as $key => $val) {
// $key == full path + filename
echo $key . PHP_EOL;
// $val == SplFileInfo instance
var_dump($val);
}
SplSubject
,SplObserver
, and SplObjectStorage
used to implement Subject/Observer pattern:
- See: https://github.com/dbierer/classic_php_examples/blob/master/oop/oop_subject_observer_storage_object.php
SplFixedArray
example: - https://github.com/dbierer/classic_php_examples/blob/master/oop/oop_spl_fixed_arr_compared_with_array_and_array_object.php Example of an autoloader that uses an array mapping technique to locate files that represent classes
- See: https://github.com/dbierer/classic_php_examples/blob/master/oop/oop_autoload_example_using_map_algorithm.php Example using recursive iterators to scan a directory structure:
<?php
$dirIt = new RecursiveDirectoryIterator(__DIR__ . '/../../php3/src/ModSPL');
$recur = new RecursiveIteratorIterator($dirIt);
foreach($recur as $name => $obj){
if ($obj->isFile()) {
echo $obj->getBasename() . PHP_EOL;
} elseif ($obj->isDir()) {
echo $obj->getPath() . PHP_EOL;
}
}
One of the best implementations for CLI is Symfony/Console
- See: https://symfony.com/doc/current/components/console.html Example using both CLI args and interactive:
<?php
$usage = "Usage: php test.php -s | -i \n";
$param = $_SERVER['argv'][1] ?? '-i';
if ($param === '-s') var_dump($argv);
$cmd = readline('What do you want to do? ');
echo $cmd . PHP_EOL;
- Also: notice that Composer has an extensive CLI capability
$ php composer.phar require
Search for a package: phpunit
Found 15 packages matching phpunit
[0] phpunit/phpunit
[1] phpunit/php-timer
[2] phpunit/php-text-template
[3] phpunit/php-file-iterator
[4] phpunit/php-code-coverage
[5] phpunit/phpunit-mock-objects Abandoned. No replacement was suggested.
[6] symfony/phpunit-bridge
[7] jean85/pretty-package-versions
[8] phpunit/php-invoker
[9] phpunit/php-token-stream Abandoned. No replacement was suggested.
[10] johnkary/phpunit-speedtrap
[11] phpstan/phpstan-phpunit
[12] brianium/paratest
[13] yoast/phpunit-polyfills
[14] spatie/phpunit-snapshot-assertions
- If you're using OOP, consider using
Symfony\Console
runStreamDb.php
:
<?php
/**
* StreamDb Runner
*/
require __DIR__ . '/../../../vendor/autoload.php';
use src\ModAdvancedTechniques\IO\StreamDb;
stream_wrapper_register('myDb', StreamDb::class);
// Stream write to a row
$user = 'vagrant';
$pwd = 'vagrant';
$host = '127.0.0.1';
$uri = 'myDb://' . $user . ':' . $pwd . '@' . $host . '/php3/1';
$resource = fopen($uri, 'w');
if($bytesAdded = fwrite($resource, 'TEST: ' . date('Y-m-d H:i:s'))) echo $bytesAdded . ' bytes Written';
fclose($resource);
// Stream read from a table row.
$resource = fopen($uri, 'r');
var_dump(fread($resource, 4096));
StreamDb.php
:
<?php
/**
* Custom Stream Wrapper and Runner
*/
namespace src\ModAdvancedTechniques\IO;
class StreamDb {
const TABLE = 'data';
const SQL_SELECT = 'SELECT * FROM `%s` WHERE id=%d';
const SQL_UPDATE = 'UPDATE `%s` SET data=:data WHERE id=:id';
const SQL_INSERT = 'INSERT INTO `%s` (id, data) VALUES (:id, :data)';
protected $stmt, $position, $data, $url, $id, $mode;
public function stream_open($url, $mode)
{
$result = FALSE;
$this->position = 0;
$url = parse_url($url);
$path = explode('/', $url['path']);
$this->id = (int) $path[2];
if (empty($this->id)) $this->id = 1;
$this->mod = $mode ?? 'r';
try{
$pdo = new \PDO("mysql:host={$url['host']};dbname={$path[1]}",
$url['user'], $url['pass'], [\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION]);
} catch(\PDOException $e){return $result;}
switch ($mode) {
case 'w' :
$pdo->exec('DELETE FROM ' . static::TABLE . ' WHERE id=' . $this->id);
$this->stmt = $pdo->prepare(sprintf(static::SQL_INSERT, static::TABLE));
break;
case 'a' :
$this->stmt = $pdo->prepare(sprintf(static::SQL_UPDATE, static::TABLE, $this->id));
case 'r' :
default :
$this->stmt = $pdo->prepare(sprintf(static::SQL_SELECT, static::TABLE, $this->id));
}
return TRUE;
}
public function stream_write($data)
{
$strlen = strlen($data);
$this->position += $strlen;
$binding = ['id' => $this->id, 'data' => $data];
//echo __METHOD__ . ':' . var_export($binding, TRUE) . ':' . var_export($this->stmt, TRUE); exit;
return $this->stmt->execute($binding) ? $strlen : null;
}
public function stream_read()
{
$this->stmt->execute();
if($this->stmt->rowCount() == 0) return false;
return implode(',', $this->stmt->fetch());
}
public function stream_tell()
{
return $this->id;
}
function stream_eof()
{
return (bool) $this->stmt->rowCount();
}
}
SQL to create table in the php3
database in the VM:
CREATE TABLE `data` (
`id` int unsigned NOT NULL,
`data` varchar(255) NOT NULL,
`mod_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
To manipulate browser caching, you can use the Cache-Control
header
- See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control Normally OpCache ignores comments (including Annotations)
- Doctrine uses these heavily
- If you're running PHP 8 or above, Doctrine will use Attributes
- See: https://www.doctrine-project.org/projects/doctrine-orm/en/2.17/reference/attributes-reference.html
- See: https://www.php.net/manual/en/language.attributes.overview.php
You can also use a php.ini
setting of on
to enable JIT:
opcache.jit=on
this is an alias fortracing
- Also, don't forget to enable opcache itself
- In addition: set a memory size for JIT (otherwise it won't work)
; example:
opcache.jit_buffer_size=32M
How do you tell if an extension is part of the "core"?
- See: https://github.com/php/php-src/tree/master/ext
How can you manipulate
php.ini
in a Docker container? - In the
Dockerfile
first install PHP - You can then do something like this:
# Make sure OpCache is enabled (should be done already!):
export PHP_INI=/etc/php.ini
sed -i 's/;zend_extension=opcache/zend_extension=opcache/g' $PHP_INI
sed -i 's/;opcache.enable=0/opcache.enable=1/g' $PHP_INI
sed -i 's/;opcache.enable_cli=0/opcache.enable_cli=1/g' $PHP_INI
sed -i 's/;opcache.memory_consumption=128/opcache.memory_consumption=128/g' $PHP_INI
Steps taken to launch an instance:
- Login to AWS
- Went to AWS marketplace
- Searched for "ZendPHP"
- Selected "ZendPHP with Apache on Ubuntu 20.04 (BYOL)"
- Clicked on "Subscribe" or "Continue to Subscribe"
- Make a note of the "EC2 Instance Types"
- Make sure everything is $0 per hour
- Click on "Accept Terms"
- From next menu clicked on "Continue to Configuration"
- Choose configuration (including region, which could affect what services are in the "Free Tier")
- Chose "US East (N. Virginia)"
- Clicked on "Continue to Launch"
- Choose Action
- Chose "Launch through EC2"
- From "Launch an Instance" menu
- Chose "t2.micro" (free tier eligible)
- Chose "Create New Key Pair"
- RSA
- PEM
- Created the directory
~/.aws
- Copied downloaded
*.pem
file to~/.aws
- Clicked "Launch Instance"
- Click "View All Instances"
- From the next screen, chose the running instance
- Wrote down Public IPv4 address
a.b.c.d
- Example:
52.90.232.221
- Example:
- Wrote down the Public IPv4 DNS
- Example:
ec2-52-90-232-221.compute-1.amazonaws.com
- Example:
- Wrote down Public IPv4 address
- Tested from browser:
http://a.b.c.d/
- Read instructions on connecting to the instance
- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AccessingInstances.html?icmpid=docs_ec2_console
- Username: ubuntu
- Set permissions on the local keypair stored in
~/.aws
- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AccessingInstances.html?icmpid=docs_ec2_console
chmod 400 key-pair-name.pem
- Shelled into instance:
ssh -i .aws/php_iii_dec_2023.pem [email protected]
- Confirm your PHP version
- Write down the version
- e.g. "8.1"
sudo zendphpctl php-list-installed
- Set up sample app in
/var/www/html
cd /var/www
sudo wget https://opensource.unlikelysource.com/post_code_test_app.zip
sudo apt install unzip
sudo unzip post_code_test_app.zip
sudo rm html/index.html
- Configure nginx for PHP-FPM
- You'll need to remove one of the
fastcgi_pass
directives- If you prefer to use the socket, be sure to check for the correct filename
- Run
ls -l /var/run/php
to confirm the name
- You'll need to remove one of the
sudo vi /etc/nginx/sites-available/default
- Restart PHP-FPM and nginx
sudo /etc/init.d/php8.1-zend-fpm restart
sudo /etc/init.d/nginx restart
- Tested from browser:
http://a.b.c.d/city=levittown&state=NY
Orchestration:
- https://www.zend.com/webinars/orchestrating-your-php-applications
- https://www.zend.com/blog/what-is-cloud-orchestration
- https://www.zend.com/blog/what-is-cloud-orchestration Example Dockerfile:
- https://github.com/dbierer/Learn-MongoDB-4.x/blob/master/docker/Dockerfile
Example
docker-compose.yml
- A 3 container orchestrated system that represents a 3 node MongoDB replica set
- https://github.com/dbierer/Learn-MongoDB-4.x/blob/master/chapters/13/docker-compose.yml Terraform templates
- https://developer.hashicorp.com/terraform/language/functions/templatefile
- https://developer.hashicorp.com/terraform/intro
Example using parse_url()
<?php
$url = 'ftp://user:[email protected]/en/services/training?id=1&view=index&component=user';
var_dump(parse_url($url));
// actual output:
/*
* array(6) {
["scheme"]=>
string(3) "ftp"
["host"]=>
string(12) "www.zend.com"
["user"]=>
string(4) "user"
["pass"]=>
string(3) "pwd"
["path"]=>
string(21) "/en/services/training"
["query"]=>
string(30) "id=1&view=index&component=user"
}
*/
- Low level example: https://github.com/dbierer/strat_post
- Good article on async programming: https://www.zend.com/blog/using-swoole-and-mezzio
Configuration Management tools
- Ansible
- Puppet
- Great source of real world data
- https://github.com/dbierer/php-iii-demos.git
- https://github.com/dbierer/php-iii-dec-2021.git
- https://github.com/dbierer/php-iii-jul-2022.git
- Something to keep your eye on:
- Machine Learning project: https://www.tensorflow.org/
- Free world city and postcode data
- geonames.org
-
Q: RE: Docker Compose: what's the difference/advantage of "ipam" vs. "overlay" for building networks?
-
A:
IPAM
is an old acronym that stands for "IP Address Management". It's not a protocol. You useipam
as a sub-key under your network service mainly to define static IP address information.- See: https://docs.docker.com/compose/compose-file/#ipam
- Also check out the "ipv4_address, ipv6_address" sub-heading under https://docs.docker.com/compose/compose-file/compose-file-v3/#networks
-
A:
overlay
is a Docker network driver that allows communication between containers. -
A: Also see this article about fixed IP addresses in Docker containers:
-
Q: Link ZendPHP Terraform templates?
-
A: See: https://www.zend.com/blog/what-is-cloud-orchestration
-
A: See: https://www.zend.com/downloads/zendphp-terraform-templates
-
Q: What's the syntax to switch between PHP versions in Ubuntu/Debian?
-
A: The utility for Debian/Ubuntu is
update-alternatives
-
A: Example using Ubuntu 20.04:
$ sudo update-alternatives --config php
There are 2 choices for the alternative php (providing /usr/bin/php).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/bin/php8.1-zend 81 auto mode
1 /usr/bin/php7.4-zend 74 manual mode
* 2 /usr/bin/php8.1-zend 81 manual mode
Press <enter> to keep the current choice[*], or type selection number:
-
A: If you're using ZendPHP, you can use
zendphpctl
to switch versions -
Q: Example of where Interfaces are used as type hints instead of classes?
-
A: Have a look at the Laminas framework:
- Most interfaces have the word "Interface" in their name
- See: https://github.com/laminas/laminas-mvc/blob/master/src/Application.php
-
A: In the Laravel framework, interfaces are generally under the
Illuminate\Contracts
namespace- Most interfaces do not have "Interface" in their name
- See: https://github.com/laravel/framework/tree/9.x/src/Illuminate/Contracts/Auth
-
Q: Example of
SplObjectStorage
used as a service container -
A: See: https://github.com/dbierer/classic_php_examples/blob/master/oop/App/Service/Manager.php
-
Q: Why is
STDOUT
still producing output even withob_start()
? -
A: Still researching. Unable to duplicate the problem on my main computer.
-
Q: Suggested
configure
options for Custom PHP Lab -
A: See below
-
Q: Other examples of SPL classic data structures:
-
A:
SplDoublyLinkedList
-
A:
SplHeap
-
A:
SplFixedArray
-
Q: Find more examples of iterators
-
A: Uses
ArrayIterator
andLimitIterator
for pagination -
A: Uses
InfiniteIterator
to build large array to demonstrate PHP 8 "stable sort" -
Q: Example of login and authentication
-
A: https://github.com/dbierer/filecms-website
- Login logic: https://github.com/dbierer/filecms-website/blob/main/templates/super/login.phtml
- Ongoing authentication: https://github.com/dbierer/filecms-website/blob/main/src/processing.php
-
A: https://github.com/dbierer/filecms-core
- Ongoing authentication verification:
- https://github.com/dbierer/filecms-core/blob/main/src/Common/Security/Profile.php
-
Q: Other examples of authentication?
-
A: Good starting point:
-
Q: Get the syntax for switching between PHP versions
-
A:
update-alternatives
-
A: see: https://askubuntu.com/questions/1373755/how-to-change-php-version-in-ubuntu-20-04-console
-
Q: Is there a major difference between
Swoole
andOpenSwoole
? -
A: OpenSwoole was forked from Swoole but nobody is willing to discuss the split.
-
A: see: swoole/swoole-src#4450
-
Q: How do you set up
pecl
in the VM? -
A: ???
-
Q: What is an "interned" string?
-
A: Any strings interned in the startup phase. Common to all the threads, won't be free'd until process exit. If we want an ability to add permanent strings even after startup, it would be still possible on costs of locking in the thread safe builds.
-
A: See: https://github.com/php/php-src/blob/master/Zend/zend_string.c
-
Q: What is
opcache.interned_strings_buffer
? -
A: The amount of memory used to store interned strings in MB
-
A: See: https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.interned-strings-buffer
-
Q: Do you have a good reference on binary trees and how they can be used to model real-life data?
-
A: See: https://stackoverflow.com/questions/2130416/what-are-the-applications-of-binary-trees
-
A: See: https://www.geeksforgeeks.org/applications-advantages-and-disadvantages-of-binary-search-tree/
-
Q: Library that allows you to bridge CPP code into a PHP extension
-
Q: Do you have examples using
SplHeap
? -
Q: What is the difference between
SplMinHeap
andSplMaxHeap
? -
A: See: https://www.php.net/manual/en/splminheap.compare.php
-
A: See: https://www.php.net/manual/en/splmaxheap.compare.php
-
A: If you wish to override
compare()
useSplHeap
-
Q: Do you have an example of form filtering using callbacks?
-
Q: Is PHP multi-threaded?
-
A: Short answer: yes, but only for CLI usage
- See: https://www.php.net/manual/en/intro.pthreads.php
- See: https://www.php.net/manual/en/intro.parallel.php
- Good explanation of how they work: https://stackoverflow.com/questions/5201852/what-is-a-thread-really
- What's the difference between how Java and PHP handle threads
- Just type the above sentence into Google ... there are hundreds of different answers!
-
Q: RE: STDIN: do you have a good example of its use?
-
A: Best bet is to read through the reference docs and look at the user comments (lots of examples)
-
Q: What some other approaches to
ENTRYPOINT
other than a simple naive loop? -
A: Examples of Dockerfiles: https://github.com/dockersamples?q=&type=all&language=&sort=stargazers
-
A: Pertinent Docker reference:
-
Q: What's the difference between
docker start
anddocker run
? -
A: The
docker run
command runs a command in a new container, pulling the image if needed and starting the container. -
A: You can restart a stopped container with all its previous changes intact using
docker start
.- Use
docker ps -a
to view a list of all containers, including those that are stopped. - See: https://docs.docker.com/engine/reference/commandline/run/
- Use
-
Q: What's the current alternative to
yum
on RedHat-based systems? -
A:
dnf
is more recently used -
Q: Pointers on PHP session management
-
A: Read this about session timeouts:
-
A: Place session files in a directory under your control:
-
A: Create your own session handler
- https://www.php.net/manual/en/function.session-set-save-handler.php
- Be sure to read the user comments!
-
Q: References to Doctrine data query cache
-
A: https://www.doctrine-project.org/projects/doctrine-orm/en/2.16/reference/caching.html
-
A: https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/second-level-cache.html
-
A: https://www.doctrine-project.org/projects/doctrine-dbal/en/3.7/reference/caching.html
-
Q: Link to Apache jMeter
-
- Installation: just download the binary
-
Q: Link to relative time formats?
-
A: All formats: https://www.php.net/manual/en/datetime.formats.php
-
A: Relative time formats: https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative
-
Q: Upload VM source code via Zoom
-
A: Will upload during class
- Also, clone this repo: https://github.com/dbierer/php-iii-demos.git
-
Q: when using @ +
time()
withDateTime()
does it default to UTC? -
A: yes, it appears to be the case:
<?php
$fmt = 'Y-m-d H:i:s';
// change to your own timezone:
echo (new DateTime('now', new DateTimezone('asia/bangkok')))->format($fmt);
echo PHP_EOL;
// both 'now' and @time() appear to default to UTC:
echo (new DateTime('now'))->format($fmt);
echo PHP_EOL;
echo (new DateTime('@' . time()))->format($fmt);
echo PHP_EOL;
- http://localhost:8883/#/5/18
- remove duplicate line:
#!/bin/bash