1. CPU


1.1. Afficher la charge du CPU

Le calcul suivant fait une différence entre deux états sur une mesure durant 1 seconde.

awk '{u=$2+$4; t=$2+$4+$5; if (NR==1){u1=u; t1=t;} else print ($2+$4-u1) * 100 / (t-t1); }' <(grep 'cpu ' /proc/stat) <(sleep 1;grep 'cpu ' /proc/stat)


1.2. En pratique

Dans le cas où un processus dépend du calcul de CPU (par exemple une simulation de battement de coeur dépendant de la charge du CPU), il peut être pertinent de faire écrire dans un fichier partagé par les deux processus la charge du CPU.

Le premier programme n'est donc pas ralenti par le calcul puisqu'il n'a qu'un cat à faire sur le fichier généré par le second programme pour le lire.

Le code suivant est un exemple du second programme. Celui-ci stocke l'utilisation du CPU dans un fichier temporaire /tmp/cpu.

#!/bin/bash
while true
do
CPU=`awk '{u=$2+$4; t=$2+$4+$5; if (NR==1){u1=u; t1=t;} else print ($2+$4-u1) * 100 / (t-t1); }' <(grep 'cpu ' /proc/stat) <(sleep 1;grep 'cpu ' /proc/stat)`;
echo $CPU > /tmp/cpu;
sleep 1;
done






2. Informations à propos de la distribution


2.1. La commande

Taper la commande suivante pour afficher des informations à propos de la distribution.

cat /etc/*-release


2.2. Exemple de sortie

DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS"
NAME="Ubuntu"
VERSION="20.04.3 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.3 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

3. Informations à propos du hardware (matériel)


3.1. Commande lshw

La commandes suivante permet d'afficher toutes les informations physiques de l'ordinateur sur lequel tourne la distribution. A savoir : disques durs internes ou externes, clefs USB, carte SD, carte graphique, carte mère, cartes mémoires, carte réseau ethernet, Wifi, Bluetooth, Clavier...

sudo lshw


La sortie n'est pas très lisible pour un humain.
A noter que le langage utilisé dépend de la configuration de l'OS (et que la traduction en FR est assez discutable...).

🧙‍♂️️Un affichage plus lisible (mais moins complet) peut s'obtenir avec la commande suivante : inxi -F


3.1.1 Exemple de sortie du lshw

description: Ordinateur Bloc-notes
produit: 20ANCTO1WW (LENOVO_MT_20AN_BU_Think_FM_ThinkPad T440p)
fabricant: LENOVO
version: ThinkPad T440p
numéro de série: PC0B9B3N
bits: 64 bits
fonctionnalités: smbios-2.7 dmi-2.7 smp vsyscall32
configuration : administrator_password=disabled chassis=notebook family=ThinkPad T440p power-on_password=disabled sku=LENOVO_MT_20AN_BU_Think_FM_ThinkPad T440p uuid=0120E439-FA54-CB11-94C3-872D38717896
*-core
description: Carte mère
produit: 20XXXTO1WW
fabricant: LENOVO
identifiant matériel: 0
version: SDXXX40697 WIN
numéro de série: L1HXXX202KH
emplacement: Not Available
*-cpu
description: CPU
produit: Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz
fabricant: Intel Corp.
identifiant matériel: 0
information bus: cpu@0
version: Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz
numéro de série: None
emplacement: CPU Socket - U3E1
taille: 2511MHz
capacité: 3500MHz
bits: 64 bits
horloge: 100MHz
fonctionnalités: lm fpu fpu_exception wp vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp x86-64 constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm cpuid_fault epb invpcid_single pti ssbd ibrs ibpb stibp tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts md_clear flush_l1d cpufreq
configuration : cores=4 enabledcores=4 threads=8
*-cache:0
description: L1 cache
identifiant matériel: 2
emplacement: L1-Cache
taille: 32KiB
capacité: 32KiB
fonctionnalités: asynchronous internal write-back instruction
configuration : level=1
*-cache:1
description: L2 cache
identifiant matériel: 3
emplacement: L2-Cache
taille: 256KiB
capacité: 256KiB
fonctionnalités: asynchronous internal write-back unified
configuration : level=2
*-cache:2
description: L3 cache
identifiant matériel: 4
emplacement: L3-Cache
taille: 6MiB
capacité: 6MiB
fonctionnalités: asynchronous internal write-back unified
configuration : level=3
*-cache
description: L1 cache
identifiant matériel: 1
emplacement: L1-Cache
taille: 32KiB
capacité: 32KiB
fonctionnalités: asynchronous internal write-back data
configuration : level=1
*-memory
description: Mémoire Système
identifiant matériel: 5
emplacement: Carte mère
taille: 16GiB
*-bank:0
description: SODIMM DDR3 Synchrone 1600 MHz (0,6 ns)
produit: M471B1G73EB0-YK0
fabricant: Samsung
identifiant matériel: 0
numéro de série: 1545EF12
emplacement: ChannelA-DIMM0
taille: 8GiB
bits: 64 bits
horloge: 1600MHz (0.6ns)
*-bank:1
description: SODIMM DDR3 Synchrone 1600 MHz (0,6 ns)
produit: M471B1G73EB0-YK0
fabricant: Samsung
identifiant matériel: 1
numéro de série: 1545EEFD
emplacement: ChannelB-DIMM0
taille: 8GiB
bits: 64 bits
horloge: 1600MHz (0.6ns)
*-firmware
description: BIOS
fabricant: LENOVO
identifiant matériel: 35
version: GLET82WW (2.36 )
date: 01/19/2016
taille: 128KiB
capacité: 12MiB
fonctionnalités: pci pnp upgrade shadowing cdboot bootselect acpi usb biosbootspecification uefi
*-pci
description: Host bridge
produit: Xeon E3-1200 v3/4th Gen Core Processor DRAM Controller
fabricant: Intel Corporation
identifiant matériel: 100
information bus: pci@0000:00:00.0
version: 06
bits: 32 bits
horloge: 33MHz
*-pci:0
description: PCI bridge
produit: Xeon E3-1200 v3/4th Gen Core Processor PCI Express x16 Controller
fabricant: Intel Corporation
identifiant matériel: 1
information bus: pci@0000:00:01.0
version: 06
bits: 32 bits
horloge: 33MHz
fonctionnalités: pci pm msi pciexpress normal_decode bus_master cap_list
configuration : driver=pcieport
ressources : irq:24
*-pci:1
description: PCI bridge
produit: Xeon E3-1200 v3/4th Gen Core Processor PCI Express x8 Controller
fabricant: Intel Corporation
identifiant matériel: 1.1
information bus: pci@0000:00:01.1
version: 06
bits: 32 bits
horloge: 33MHz
fonctionnalités: pci pm msi pciexpress normal_decode bus_master cap_list
configuration : driver=pcieport
ressources : irq:25 portE/S:3000(taille=4096) mémoire:f0000000-f10fffff portE/S:c0000000(taille=301989888)
*-display
description: VGA compatible controller
produit: GK208M [GeForce GT 730M]
fabricant: NVIDIA Corporation
identifiant matériel: 0
information bus: pci@0000:02:00.0
version: a1
bits: 64 bits
horloge: 33MHz
fonctionnalités: pm msi pciexpress vga_controller bus_master cap_list rom
configuration : driver=nvidia latency=0
ressources : irq:44 mémoire:f0000000-f0ffffff mémoire:c0000000-cfffffff mémoire:d0000000-d1ffffff portE/S:3000(taille=128) mémoire:f1080000-f10fffff
*-multimedia
description: Audio device
produit: GK208 HDMI/DP Audio Controller
fabricant: NVIDIA Corporation
identifiant matériel: 0.1
information bus: pci@0000:02:00.1
version: a1
bits: 32 bits
horloge: 33MHz
fonctionnalités: pm msi pciexpress bus_master cap_list
configuration : driver=snd_hda_intel latency=0
ressources : irq:18 mémoire:f1000000-f1003fff
*-display
description: VGA compatible controller
produit: 4th Gen Core Processor Integrated Graphics Controller
fabricant: Intel Corporation
identifiant matériel: 2
information bus: pci@0000:00:02.0
version: 06
bits: 64 bits
horloge: 33MHz
fonctionnalités: msi pm vga_controller bus_master cap_list rom
configuration : driver=i915 latency=0
ressources : irq:34 mémoire:f1400000-f17fffff mémoire:e0000000-efffffff portE/S:4000(taille=64) mémoire:c0000-dffff
*-multimedia:0
description: Audio device
produit: Xeon E3-1200 v3/4th Gen Core Processor HD Audio Controller
fabricant: Intel Corporation
identifiant matériel: 3
information bus: pci@0000:00:03.0
version: 06
bits: 64 bits
horloge: 33MHz
fonctionnalités: pm msi pciexpress bus_master cap_list
configuration : driver=snd_hda_intel latency=0
ressources : irq:43 mémoire:f1a30000-f1a33fff
*-usb:0
description: USB controller
produit: 8 Series/C220 Series Chipset Family USB xHCI
fabricant: Intel Corporation
identifiant matériel: 14
information bus: pci@0000:00:14.0
version: 04
bits: 64 bits
horloge: 33MHz
fonctionnalités: pm msi xhci bus_master cap_list
configuration : driver=xhci_hcd latency=0
ressources : irq:28 mémoire:f1a20000-f1a2ffff
*-usbhost:0
produit: xHCI Host Controller
fabricant: Linux 5.4.0-90-generic xhci-hcd
identifiant matériel: 0
information bus: usb@3
nom logique: usb3
version: 5.04
fonctionnalités: usb-2.00
configuration : driver=hub slots=15 speed=480Mbit/s
*-usb:0
description: Clavier
produit: USB Receiver
fabricant: Logitech
identifiant matériel: 1
information bus: usb@3:1
version: 24.11
fonctionnalités: usb-2.00
configuration : driver=usbhid maxpower=98mA speed=12Mbit/s
*-usb:1
description: Clavier
produit: 2.4G Receiver
fabricant: Compx
identifiant matériel: 6
information bus: usb@3:6
version: 2.41
fonctionnalités: usb-1.10
configuration : driver=usbhid maxpower=100mA speed=12Mbit/s
*-usb:2 NON-RÉCLAMÉ
description: Périphérique USB générique
produit: VFS 5011 fingerprint sensor
fabricant: Validity Sensors, Inc.
identifiant matériel: 7
information bus: usb@3:7
version: 0.78
numéro de série: c193c6061516
fonctionnalités: usb-1.10
configuration : maxpower=100mA speed=12Mbit/s
*-usb:3
description: Interface sans fil Bluetooth
fabricant: Intel Corp.
identifiant matériel: b
information bus: usb@3:b
version: 0.01
fonctionnalités: bluetooth usb-2.00
configuration : driver=btusb maxpower=100mA speed=12Mbit/s
*-usb:4
description: Vidéo
produit: Integrated Camera
fabricant: SunplusIT INC.
identifiant matériel: c
information bus: usb@3:c
version: 0.03
fonctionnalités: usb-2.00
configuration : driver=uvcvideo maxpower=500mA speed=480Mbit/s
*-usbhost:1
produit: xHCI Host Controller
fabricant: Linux 5.4.0-90-generic xhci-hcd
identifiant matériel: 1
information bus: usb@4
nom logique: usb4
version: 5.04
fonctionnalités: usb-3.00
configuration : driver=hub slots=6 speed=5000Mbit/s
*-usb
description: Périphérique de stockage de masse
produit: Elements 2620
fabricant: Western Digital
identifiant matériel: 2
information bus: usb@4:2
version: 10.23
numéro de série: 575837324436314145563344
fonctionnalités: usb-3.20 scsi
configuration : driver=usb-storage maxpower=896mA speed=5000Mbit/s
*-communication
description: Communication controller
produit: 8 Series/C220 Series Chipset Family MEI Controller #1
fabricant: Intel Corporation
identifiant matériel: 16
information bus: pci@0000:00:16.0
version: 04
bits: 64 bits
horloge: 33MHz
fonctionnalités: pm msi bus_master cap_list
configuration : driver=mei_me latency=0
ressources : irq:32 mémoire:f1a39000-f1a3900f
*-network
description: Ethernet interface
produit: Ethernet Connection I217-LM
fabricant: Intel Corporation
identifiant matériel: 19
information bus: pci@0000:00:19.0
nom logique: eth0
version: 04
numéro de série: 50:7b:9d:7f:f4:35
capacité: 1Gbit/s
bits: 32 bits
horloge: 33MHz
fonctionnalités: pm msi bus_master cap_list ethernet physical tp 10bt 10bt-fd 100bt 100bt-fd 1000bt-fd autonegotiation
configuration : autonegotiation=on broadcast=yes driver=e1000e driverversion=3.2.6-k firmware=0.13-3 latency=0 link=no multicast=yes port=twisted pair
ressources : irq:30 mémoire:f1a00000-f1a1ffff mémoire:f1a3f000-f1a3ffff portE/S:4080(taille=32)
*-usb:1
description: USB controller
produit: 8 Series/C220 Series Chipset Family USB EHCI #2
fabricant: Intel Corporation
identifiant matériel: 1a
information bus: pci@0000:00:1a.0
version: 04
bits: 32 bits
horloge: 33MHz
fonctionnalités: pm debug ehci bus_master cap_list
configuration : driver=ehci-pci latency=0
ressources : irq:16 mémoire:f1a3e000-f1a3e3ff
*-usbhost
produit: EHCI Host Controller
fabricant: Linux 5.4.0-90-generic ehci_hcd
identifiant matériel: 1
information bus: usb@1
nom logique: usb1
version: 5.04
fonctionnalités: usb-2.00
configuration : driver=hub slots=3 speed=480Mbit/s
*-usb
description: Concentrateur USB
fabricant: Intel Corp.
identifiant matériel: 1
information bus: usb@1:1
version: 0.04
fonctionnalités: usb-2.00
configuration : driver=hub slots=6 speed=480Mbit/s
*-multimedia:1
description: Audio device
produit: 8 Series/C220 Series Chipset High Definition Audio Controller
fabricant: Intel Corporation
identifiant matériel: 1b
information bus: pci@0000:00:1b.0
version: 04
bits: 64 bits
horloge: 33MHz
fonctionnalités: pm msi pciexpress bus_master cap_list
configuration : driver=snd_hda_intel latency=0
ressources : irq:36 mémoire:f1a34000-f1a37fff
*-pci:2
description: PCI bridge
produit: 8 Series/C220 Series Chipset Family PCI Express Root Port #1
fabricant: Intel Corporation
identifiant matériel: 1c
information bus: pci@0000:00:1c.0
version: d4
bits: 32 bits
horloge: 33MHz
fonctionnalités: pci pciexpress msi pm normal_decode bus_master cap_list
configuration : driver=pcieport
ressources : irq:26 mémoire:f1900000-f19fffff
*-generic
description: Unassigned class
produit: RTS5227 PCI Express Card Reader
fabricant: Realtek Semiconductor Co., Ltd.
identifiant matériel: 0
information bus: pci@0000:03:00.0
version: 01
bits: 32 bits
horloge: 33MHz
fonctionnalités: pm msi pciexpress bus_master cap_list
configuration : driver=rtsx_pci latency=0
ressources : irq:29 mémoire:f1900000-f1900fff
*-pci:3
description: PCI bridge
produit: 8 Series/C220 Series Chipset Family PCI Express Root Port #2
fabricant: Intel Corporation
identifiant matériel: 1c.1
information bus: pci@0000:00:1c.1
version: d4
bits: 32 bits
horloge: 33MHz
fonctionnalités: pci pciexpress msi pm normal_decode bus_master cap_list
configuration : driver=pcieport
ressources : irq:27 mémoire:f1800000-f18fffff
*-network
description: Interface réseau sans fil
produit: Wireless 7260
fabricant: Intel Corporation
identifiant matériel: 0
information bus: pci@0000:04:00.0
nom logique: wlan0
version: bb
numéro de série: 58:91:cf:76:9e:f9
bits: 64 bits
horloge: 33MHz
fonctionnalités: pm msi pciexpress bus_master cap_list ethernet physical wireless
configuration : broadcast=yes driver=iwlwifi driverversion=5.4.0-90-generic firmware=17.3216344376.0 ip=192.168.1.111 latency=0 link=yes multicast=yes wireless=IEEE 802.11
ressources : irq:33 mémoire:f1800000-f1801fff
*-usb:2
description: USB controller
produit: 8 Series/C220 Series Chipset Family USB EHCI #1
fabricant: Intel Corporation
identifiant matériel: 1d
information bus: pci@0000:00:1d.0
version: 04
bits: 32 bits
horloge: 33MHz
fonctionnalités: pm debug ehci bus_master cap_list
configuration : driver=ehci-pci latency=0
ressources : irq:23 mémoire:f1a3d000-f1a3d3ff
*-usbhost
produit: EHCI Host Controller
fabricant: Linux 5.4.0-90-generic ehci_hcd
identifiant matériel: 1
information bus: usb@2
nom logique: usb2
version: 5.04
fonctionnalités: usb-2.00
configuration : driver=hub slots=3 speed=480Mbit/s
*-usb
description: Concentrateur USB
fabricant: Intel Corp.
identifiant matériel: 1
information bus: usb@2:1
version: 0.04
fonctionnalités: usb-2.00
configuration : driver=hub slots=8 speed=480Mbit/s
*-isa
description: ISA bridge
produit: QM87 Express LPC Controller
fabricant: Intel Corporation
identifiant matériel: 1f
information bus: pci@0000:00:1f.0
version: 04
bits: 32 bits
horloge: 33MHz
fonctionnalités: isa bus_master cap_list
configuration : driver=lpc_ich latency=0
ressources : irq:0
*-sata
description: SATA controller
produit: 8 Series/C220 Series Chipset Family 6-port SATA Controller 1 [AHCI mode]
fabricant: Intel Corporation
identifiant matériel: 1f.2
information bus: pci@0000:00:1f.2
version: 04
bits: 32 bits
horloge: 66MHz
fonctionnalités: sata msi pm ahci_1.0 bus_master cap_list
configuration : driver=ahci latency=0
ressources : irq:31 portE/S:40a8(taille=8) portE/S:40b4(taille=4) portE/S:40a0(taille=8) portE/S:40b0(taille=4) portE/S:4060(taille=32) mémoire:f1a3c000-f1a3c7ff
*-serial
description: SMBus
produit: 8 Series/C220 Series Chipset Family SMBus Controller
fabricant: Intel Corporation
identifiant matériel: 1f.3
information bus: pci@0000:00:1f.3
version: 04
bits: 64 bits
horloge: 33MHz
configuration : driver=i801_smbus latency=0
ressources : irq:18 mémoire:f1a38000-f1a380ff portE/S:efa0(taille=32)
*-pnp00:00
produit: PnP device PNP0c01
identifiant matériel: 2
fonctionnalités: pnp
configuration : driver=system
*-pnp00:01
produit: PnP device PNP0c02
identifiant matériel: 3
fonctionnalités: pnp
configuration : driver=system
*-pnp00:02
produit: PnP device PNP0b00
identifiant matériel: 4
fonctionnalités: pnp
configuration : driver=rtc_cmos
*-pnp00:03
produit: PnP device LEN0071
fabricant: Lenovo Group Limited
identifiant matériel: 6
fonctionnalités: pnp
configuration : driver=i8042 kbd
*-pnp00:04
produit: PnP device LEN0036
fabricant: Lenovo Group Limited
identifiant matériel: 7
fonctionnalités: pnp
configuration : driver=i8042 aux
*-pnp00:05
produit: PnP device SMO1200
fabricant: STMicroelectronics
identifiant matériel: 8
fonctionnalités: pnp
configuration : driver=tpm_tis
*-scsi:0
identifiant matériel: 9
nom logique: scsi0
fonctionnalités: emulated
*-disk
description: ATA Disk
produit: Samsung SSD 870
identifiant matériel: 0.0.0
information bus: scsi@0:0.0.0
nom logique: /dev/sda
version: 1B6Q
numéro de série: S626NJ0R336697P
taille: 931GiB (1TB)
fonctionnalités: gpt-1.00 partitioned partitioned:gpt
configuration : ansiversion=5 guid=21d31760-00d1-4580-8407-40f3e2afecbd logicalsectorsize=512 sectorsize=512
*-volume:0
description: Windows FAT volume
fabricant: mkfs.fat
identifiant matériel: 1
information bus: scsi@0:0.0.0,1
nom logique: /dev/sda1
nom logique: /boot/efi
version: FAT32
numéro de série: 9da5-06f1
taille: 510MiB
capacité: 511MiB
fonctionnalités: boot fat initialized
configuration : FATs=2 filesystem=fat mount.fstype=vfat mount.options=rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro name=EFI System Partition state=mounted
*-volume:1
description: EFI partition
fabricant: Linux
identifiant matériel: 2
information bus: scsi@0:0.0.0,2
nom logique: /dev/sda2
nom logique: /boot
version: 1.0
numéro de série: 9d3d1bc1-1324-4311-b456-3d4ab82e1dd1
taille: 244MiB
fonctionnalités: extended_attributes ext2 initialized
configuration : filesystem=ext2 lastmountpoint=/boot modified=2021-11-17 08:53:08 mount.fstype=ext2 mount.options=rw,relatime mounted=2021-11-17 08:53:08 state=mounted
*-volume:2
description: LVM Physical Volume
fabricant: Linux
identifiant matériel: 3
information bus: scsi@0:0.0.0,3
nom logique: /dev/sda3
numéro de série: nneTrT-1234-JX8e-f56t-QQZh-5125-SRs40T
taille: 118GiB
capacité: 145GiB
fonctionnalités: multi lvm2
*-volume:3
description: Volume EXT4
fabricant: Linux
identifiant matériel: 4
information bus: scsi@0:0.0.0,4
nom logique: /dev/sda4
nom logique: /mnt/fc04d9a6-1340-4f9a-ac24-a7995f271517
version: 1.0
numéro de série: fc04d9a6-1340-4f9a-ac24-a7995f271517
taille: 785GiB
fonctionnalités: journaled extended_attributes large_files huge_files dir_nlink recover extents ext4 ext2 initialized
configuration : created=2021-08-01 23:25:00 filesystem=ext4 lastmountpoint=/mnt/fc04d9a6-1340-4f9a-ac24-a7995f271517 modified=2021-11-17 08:53:08 mount.fstype=ext4 mount.options=rw,nosuid,nodev,relatime mounted=2021-11-17 08:53:08 name=Stockage state=mounted
*-scsi:1
identifiant matériel: a
nom logique: scsi5
fonctionnalités: emulated
*-cdrom
description: DVD-RAM writer
produit: DVDRAM GUB0N
fabricant: HL-DT-ST
identifiant matériel: 0.0.0
information bus: scsi@5:0.0.0
nom logique: /dev/cdrom
nom logique: /dev/cdrw
nom logique: /dev/dvd
nom logique: /dev/dvdrw
nom logique: /dev/sr0
version: LV20
fonctionnalités: removable audio cd-r cd-rw dvd dvd-r dvd-ram
configuration : ansiversion=5 status=nodisc
*-scsi:2
identifiant matériel: b
nom logique: scsi7
fonctionnalités: emulated scsi-host
configuration : driver=usb-storage
*-disk
description: SCSI Disk
produit: Elements 2620
fabricant: WD
identifiant matériel: 0.0.0
information bus: scsi@7:0.0.0
nom logique: /dev/sdc
version: 1023
numéro de série: WX72D61AEV3D
taille: 4657GiB (5TB)
fonctionnalités: gpt-1.00 partitioned partitioned:gpt
configuration : ansiversion=6 guid=4977e774-1340-43be-b71a-9ec0411a4123 logicalsectorsize=512 sectorsize=4096
*-volume
description: Windows NTFS volume
fabricant: Windows
identifiant matériel: 1
information bus: scsi@7:0.0.0,1
nom logique: /dev/sdc1
nom logique: /media/dmeloni/Name
version: 3.1
numéro de série: 0ac0-1234
taille: 561GiB
capacité: 4657GiB
fonctionnalités: ntfs initialized
configuration : clustersize=4096 created=2021-11-01 17:04:39 filesystem=ntfs label=Name mount.fstype=fuseblk mount.options=rw,nosuid,nodev,relatime,user_id=0,group_id=0,default_permissions,allow_other,blksize=4096 state=mounted
*-battery
produit: Batt Device Name
fabricant: Batt Manufacture
identifiant matériel: 1
emplacement: Front


3.2. Commande inxi -F

inxi doit être installé sur le système pour être utilisé.
Le résultat de la commande est plus lisible pour un humain que celui d lshw.

inxi -F


A noter que la commande doit être lancée en root pour afficher des données plus sensibles (numéros de série).

3.2.1 Exemple de sortie de inxi -F

System: Host: dmeloni Kernel: 5.4.0-90-generic x86_64 bits: 64 Desktop: Gnome 3.36.9 Distro: Ubuntu 20.04.3 LTS (Focal Fossa)
Machine: Type: Laptop System: LENOVO product: 20ANCTO1WW v: ThinkPad T440p serial: <superuser/root required>
Mobo: LENOVO model: 20ANCTO1WW v: SDK0J40697 WIN serial: <superuser/root required> UEFI: LENOVO v: GLET82WW (2.36 )
date: 01/19/2016
Battery: ID-1: BAT0 charge: 20.6 Wh condition: 20.6/99.5 Wh (21%)
CPU: Topology: Quad Core model: Intel Core i7-4710MQ bits: 64 type: MT MCP L2 cache: 6144 KiB
Speed: 1520 MHz min/max: 800/3500 MHz Core speeds (MHz): 1: 1197 2: 1197 3: 1197 4: 1197 5: 1197 6: 1197 7: 1197
8: 1198
Graphics: Device-1: Intel 4th Gen Core Processor Integrated Graphics driver: i915 v: kernel
Device-2: NVIDIA GK208M [GeForce GT 730M] driver: nvidia v: 390.144
Display: x11 server: X.Org 1.20.11 driver: fbdev unloaded: modesetting,vesa
resolution: 1920x1080~60Hz, 1920x1080~60Hz
OpenGL: renderer: GeForce GT 730M/PCIe/SSE2 v: 4.6.0 NVIDIA 390.144
Audio: Device-1: Intel Xeon E3-1200 v3/4th Gen Core Processor HD Audio driver: snd_hda_intel
Device-2: Intel 8 Series/C220 Series High Definition Audio driver: snd_hda_intel
Device-3: NVIDIA GK208 HDMI/DP Audio driver: snd_hda_intel
Sound Server: ALSA v: k5.4.0-90-generic
Network: Device-1: Intel Ethernet I217-LM driver: e1000e
IF: eth0 state: down mac: 50:7b:9d:7f:f4:35
Device-2: Intel Wireless 7260 driver: iwlwifi
IF: wlan0 state: up mac: 58:91:cf:76:9e:f9
IF-ID-1: docker0 state: down mac: 02:42:be:61:90:26
IF-ID-2: ppp0 state: unknown speed: N/A duplex: N/A mac: N/A
Drives: Local Storage: total: 5.69 TiB used: 1.05 TiB (18.4%)
ID-1: /dev/mmcblk0 vendor: Swissbit model: SF256 size: 238.30 GiB
ID-2: /dev/sda vendor: Samsung model: SSD 870 EVO 1TB size: 931.51 GiB
ID-3: /dev/sdc type: USB vendor: Western Digital model: WD Elements 2620 size: 4.55 TiB
Partition: ID-1: / size: 100.85 GiB used: 94.94 GiB (94.1%) fs: ext4 dev: /dev/dm-0
ID-2: /boot size: 236.3 MiB used: 152.5 MiB (64.5%) fs: ext2 dev: /dev/sda2
ID-3: swap-1 size: 15.88 GiB used: 163.0 MiB (1.0%) fs: swap dev: /dev/dm-1
Sensors: System Temperatures: cpu: 128.0 C mobo: 0.0 C gpu: nvidia temp: 80 C
Fan Speeds (RPM): cpu: 3437
Info: Processes: 486 Uptime: 7h 27m Memory: 15.52 GiB used: 9.64 GiB (62.1%) Shell: bash inxi: 3.0.38

4. Informations à propos du noyau Linux


4.1. La commande

Taper la commande suivante pour afficher des informations à propos du noyau.

cat /proc/version


4.2. Exemple de sortie

Linux version 5.4.0-90-generic (buildd@lgw01-amd64-054) (gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)) #101-Ubuntu SMP Fri Oct 15 20:00:55 UTC 2021


5. Processus


5.1. Astuces


5.1.1 Grep sur la liste de ps

Quand on fait un ps aux | grep process, le résultat retourne également cette commande en elle même.
Etant donné que le paramètre de grep est une regex, on peut donc utiliser les crochets de cette façon-ci :

ps aux | grep [n]om_du_processus


la ligne sera du coup ignorée puisque la chaine "[n]om_du_processus" ne correspond pas à la regex "[n]om_du_processus" (oui c'est un peu tordu mais logique).

📖️️Sources :
*http://shaarli.guiguishow.info/?w5t54A
*https://dukeart.netlib.re/links/?YZrrPQ


A noter que si l'interet de faire le grep est de savoir si un processus existe, on peut également utiliser "pgrep nom_du_processus" qui retournera tous les pids correspondants.

6. La commande ssh de A à X


📖️️Tout ce qui suit est une copie partielle (et parfois corrigée) de la page suivante : https://wiki.debian.org/fr/SSH


6.1. Introduction

SSH1 (« Secure SHell ») est un protocole de communication sécurisé. Il permet, entre autres, de se connecter à un hôte à distance de façon sécurisée sur un réseau qui ne l'est pas forcément. Rendez-vous sur Wikipedia - Secure Shell pour plus d'informations, ainsi que ssh, lsh-client ou dropbear pour découvrir quelques implémentations du protocole SSH présentes dans Debian, avec bien sûr OpenSSH qui est probablement la plus populaire2. SSH remplace et améliore telnet,rlogin et rsh, qui sont tous des protocoles de communication à distance mais n'incluant aucune couche de sécurité.

Dans ce document nous allons utiliser la suite OpenSSH, et il sera également supposé que les deux variables suivantes seront définies :

hôte_distant=<l'ordinateur à distance>
utilisateur_distant=<nom d'utilisateur sur $hôte_distant>

Par conséquent, pour reproduire les commandes ci-dessous, veillez à bien remplacer ces valeurs par celles qui vous intéressent. Notez aussi que « hôte_distant » peut être une adresse IP.

6.2. Installation

6.2.1 Installation du client

Normalement, le client est installé par défaut, sinon il suffit de saisir en tant que superutilisateur :

apt install openssh-client

6.2.2 Installation du serveur

La partie serveur permet à des hôtes distants de se connecter à votre système et sera installée comme suit en tant que superutilisateur :

apt install openssh-server

6.3. Fichiers de configuration

Les principaux fichiers de configuration sont dans le répertoire /etc/ssh :
* ssh_config : fichier de configuration du client
* sshd_config : fichier de configuration du serveur

De plus, ce répertoire contient les couples de clés privées/publiques identifiant vos hôtes :

* ssh_host_dsa_key
* ssh_host_dsa_key.pub
* ssh_host_rsa_key
* ssh_host_rsa_key.pub

Depuis OpenSSH 5.73, une nouvelle paire de clés a fait son apparition :
* ssh_host_ecdsa_key
* ssh_host_ecdsa_key.pub
*
Depuis OpenSSH 6.54, une nouvelle paire de clés a fait son apparition :
* ssh_host_ed25519_key
* ssh_host_ed25519_key.pub

6.3.1 Régénérer les clés hôte

rm /etc/ssh/ssh_host_*
dpkg-reconfigure openssh-server


6.4. Accès sur une machine distante

6.4.1 Avec mot de passe

Si vous voulez vous connecter à la machine $hôte_distant avec l'utilisateur $utilisateur_distant, saisissez simplement

ssh $utilisateur_distant@$hôte_distant

et saisir votre mot de passe.

Si le nom d'utilisateur sur le système local et la machine distante sont les mêmes, vous pouvez omettre la partie $utilisateur_distant@ et saisir simplement

ssh $hôte_distant

Si c'est la première fois que vous vous connectez à l'hôte distant, le client ssh va vous demander si vous êtes certain de vouloir vous connecter à la machine distante. Répondez « oui » après avoir vérifié l'empreinte du serveur et entrez votre mot de passe, ainsi, ssh vous connectera à l'hôte distant.

6.4.2 Utilisation de clés partagées

Une des fonctionnalités supportées par SSH est l'utilisation d'un couple de clés privée/publique pour se connecter à l'hôte distant. Également nommée SSH keys, cette méthode permet, si l'on se connecte souvent à un hôte distant de ne pas saisir à chaque fois son mot de passe. Pour cela, vous devez générer un couple de clés privée/publique sur votre machine locale et déposer la clé publique sur l'hôte distant.

Pour générer la clé, on utilise le programme ssh-keygen

ssh-keygen -t rsa

Ce programme va générer un couple de clés privé/publique dans le répertoire ~/.ssh. Le programme vous demande dans un premier temps le répertoire de destination des clés, par défaut votre répertoire personnel, une phrase de passe (passphrase) vous est ensuite demandé.

Notes : nous vous conseillons de ne pas laisser la passphrase vide. En effet, un attaquant qui aurait récupéré votre clé privée pourrait ensuite se connecter facilement à tous les hôtes sur lesquels vous avez déposé votre clé publique si la phrase de passe est vide. Pensez à choisir une passphrase longue et complexe.

Votre clé privée est id_rsa (vous ne devez jamais la fournir à un tier), votre clé publique est id_rsa.pub.

Vous devez copier votre clé publique sur l'hôte distant avec la commande ssh-copy-id

ssh-copy-id -i ~/.ssh/id_rsa.pub $utilisateur_distant@$hôte_distant

Vous pouvez maintenant vous connecter simplement à l'hôte distant, la passphrase vous est demandée. Une fois rentré, vous êtes connecté à l'hôte distant. En cas de nouvelles connections, la passphrase ne vous sera plus demandé durant toute votre session.

6.5. Sécurisation

6.5.1 Serveur SSH

Par défaut, un serveur SSH est relativement sécurisé. Il est possible, à l'aide de quelques options de configuration ou d'outils, de le rendre encore plus difficile à cracker.

💡️L'utilisation de la dernière version disponible du paquet openssh-server permet de se protéger contre l'utilisation des failles de sécurité connues.

💡️Activez l'authentification par les keys SSH uniquement avec des passphrases (ie. désactivez l'authentification par le seul mot de passe).

💡️Vous pouvez utiliser fail2ban qui est un logiciel de surveillance de fichiers de journal bannissant automatiquement une adresse IP après un certain nombre prédéfini de tentatives de connexion échouées. Il permet de se prémunir automatiquement des attaques par force brute.

📖️️De bonnes pratiques pour l'utilisation de SSH se trouvent à https://lackof.org/taggart/hacking/ssh/


6.5.1.1 Options de configuration

Il faut éditer le fichier /etc/ssh/sshd_config pour ajuster le paramétrage puis relancer le serveur SSH par

service ssh restart


Désactiver l'utilisation du mot de passe pour l'authentification (PasswordAuthentication no)

Désactiver l'utilisation du compte root (PermitRootLogin no)

N'autoriser que certains utilisateurs ou groupes d'utilisateurs à se connecter (AllowUsers et AllowGroups)

📖️️Les options AllowUsers et AllowGroups n'amélioreront pas la sécurité d'un serveur SSH. Mais, dans certains cas, leur utilisation permet de résister un peu plus longtemps lors des attaques par force brute.


6.5.1.2 Outils externes

fail2ban : permet de « blacklister » automatiquement à l'aide de iptables les adresses IP tentant d'attaquer par « force brute » un serveur SSH.

denyhosts : comme fail2ban, denyhosts permet de bloquer les adresses IP tentant de forcer une connexion SSH. Mais contrairement à fail2ban, il n'utilise pas iptables, mais le fichier « /etc/hosts.deny ».

6.5.2 Client SSH

6.5.2.1 Bonnes pratiques avec le client SSH

💡️L'utilisation de la dernière version disponible du paquet openssh-client permet de se protéger contre l'utilisation des failles de sécurité connues.

💡️Utilisez l'authentification par les clés SSH plutôt que par le mot de passe.

💡️Ajoutez des mots de passe robustes à vos clés SSH. Cela réduit les risques d'attaque par force brute.


📖️️Allez + loin : https://wiki.debian.org/fr/SSH




7. Transférer un document vers un serveur


7.1. Copier un fichier via scp : secure copy

scp est un programme qui permet de copier un fichier de façon sécurisée entre deux serveurs (utilise pour cela SSH).

La syntaxe de la commande est un peu compliquée pour un débutant. On s'y fait avec le temps.

7.1.1 Copier un fichier localement avec scp

On peut utiliser scp au lieu de cp si l'on souhaite copier un fichier localement :

scp source_file target_file


7.1.2 Copier un fichier du serveur local vers le serveur distant

Par exemple, voici un exemple pour transférer le fichier local_file dans le dossier tmp du serveur dont l'IP serait XXX.0.0.1 et le nom d'utilisateur username.

scp local_file username@XXX.0.0.1:/tmp/server_file


💡️Pour copier un dossier, il suffit ajouter l'option -r à la commande scp.


7.1.3 Copier un fichier du serveur distant vers le serveur local

Il suffit d'inverser les deux parties de cette façon-ci :

scp username@XXX.0.0.1:/tmp/server_file local_file


7.2. Reprendre le téléchargement d'un fichier avec rsync

Contexte : vous êtes en train de transférer un fichier assez volumineux vers un serveur, mais le téléchargement s'arrête prématurément : panique à bord, faut-il le relancer de zéro ?
Evidemment, il existe une solution : rsync !

Le rôle du programme rsync est de synchroniser le contenu des fichiers entre deux serveurs.

Voici un exemple pour relancer le téléchargement d'un fichier :

rsync -P -e ssh username@XXX.0.0.1:/tmp/server_file local_file


🧙‍♂️️Pour copier un fichier, il est donc possible de n'utiliser que rsync.
Cependant, la synchronisation étant bidirectionnelle entre les serveurs, cela peut être dangereux car les fichiers du serveur source peuvent tout à fait être effacés par la manoeuvre.


8. La commande rm de A à X

La commande rm ("remove") est une commande vitale qui permet de supprimer des fichiers ou des dossiers.

8.1. Pour supprimer un fichier

rm fichier.txt


8.2. Pour supprimer un dossier

8.2.1 Supprimer un dossier non vide (-r)

On peut procéder de deux manières :

rm -r dossier


💡️On peut également utiliser -R ou --recursive à la place de -r


8.2.2 Supprimer un dossier vide (-d)

Dans le cas où l'on souhaite supprimer un dossier vide, voici la commande que l'on peut effectuer :

rm -d dossier_vide


💡️On peut également utiliser la version longue --dir à la place de -d

💡️La commande rmdir existe également.


8.2.3 Astuce : supprimer un dossier contenant trop d'éléments

Dans le cas où un dossier contient beaucoup trop de fichiers, le rm ne fonctionne pas.

Voici le message qui s'afficherait sur un Debian anglais :

# cd dossier;
rm  *
# RETOUR : 
# -bash: /bin/rm: Argument list too long


On peut alors procéder de cette façon-là :

# cd dossier;
ls | xargs rm


💡️Voici une version assez agressive mais très efficace qui utilise les options -r et -f : `ls | xargs rm -rf`


En plus séquentiel, le code ci-dessus équivaudrait à ceci :

# cd dossier;
ls | while read param
do
  rm param
done


8.3. Supprimer plusieurs fichiers en une seule commande

Les arguments de rm peuvent être autant de noms de fichiers ou de chemins :

rm fichier_1 fichier_2;


8.4. Ignorer les fichiers inexistants (-f)

Contrairement à une idée répandue, l'option --force n'a d'effet que si l'un des fichiers en argument n'existe pas.

Par exemple, supposons que nous nous trouvons dans un répertoire où les fichiers fichier_1, fichier_3 et fichier_4 existent (fichier_2 n'existe donc pas).

Voici ce que donnerait l'utilisation du rm sans l'option --force :

# touch fichier_1  fichier_3 fichier_4
# denis@denis:/tmp/test $ ls
# fichier_1  fichier_3 fichier_4
rm fichier_1 fichier_2 fichier_3
# RETOUR :
# rm: impossible de supprimer 'fichier_2': Aucun fichier ou dossier de ce type
# $? vaut 1 : erreur
# Le ls retournera cela 
# fichier_4

Ici les fichiers 1 et 2 ont été supprimés mais le rm a retourné une erreur.
Ce comportement pourrait être problématique dans un script bash car le script pourrait s'arreter alors qu'il ne s'agit que d'une erreur mineure (ie. Le fichier fichier_2 est déjà supprimé).

Maintenant, le même exemple avec l'option --force (ou -f) :

# touch fichier_1  fichier_3 fichier_4
# denis@denis:/tmp/test $ ls
# fichier_1  fichier_3 fichier_4
rm -f fichier_1 fichier_2 fichier_3
# RETOUR : vide
# $? vaut 0 : ok
# Le ls retournera toujours cela
# fichier_4

Ici les fichiers 1 et 2 ont été supprimés sans que le rm retourne une erreur.

Ainsi, lorsque l'on cumule les options -f et -r, cela ne veut pas dire "forcer la suppression de tous les fichiers et répertoires", cela veut juste dire "supprime tous les fichiers et répertoire et ignore les fichiers qui n'existent pas".

Si l'on souhaite supprimer un fichier sans en avoir les droits d'écriture, le rm retournera une erreur, que l'option --force soit utilisée ou non. A la différence que la commande ne demande pas à l'utilisateur de confirmer son choix.

Résultat sans le --force :

rm /etc/sudoers
# RETOUR :
# rm: supprimer '/etc/sudoers' qui est protégé en écriture ? y
# rm: impossible de supprimer '/etc/sudoers': Permission non accordée
# $? vaut 1 : erreur


Résultat avec le --force :

rm -f /etc/sudoers
# RETOUR :
# rm: impossible de supprimer '/etc/sudoers': Permission non accordée
# $? vaut 1 : erreur

Ici encore, il parait pertinent d'utiliser l'option --force dans le cadre d'un script automatique qui ne demanderait pas à l'utilisateur de valider la suppression du fichier.
Cependant, il existe également l'option --interactive qui permet d'indiquer à la commande de façon explicite si l'on souhaite ou non que la commande pose des questions.

8.4.1 Demander à l'utilisateur de confirmer la suppression à chaque fichier (-i)

Si l'on souhaite que l'utilisateur valide chaque suppression, on peut utiliser l'option -i ou encore son équivalent verbeux --interactive=always

Voici un exemple :

touch fichier_1 fichier_2 fichier_3
rm -i fichier_*
# RETOUR : 
# rm : supprimer 'fichier_1' du type fichier vide ? n
# rm : supprimer 'fichier_2' du type fichier vide ? n
# rm : supprimer 'fichier_3' du type fichier vide ? n


Pour chaque fichier correspondant aux arguments, une confirmation est demandée à l'utilisateur.

Cette option peut avoir un intéret dans le cas où écrire les noms des fichiers à supprimer est fastidieux.

8.4.2 Combiner les options -r et -i

Si l'on combine les deux options -r et -i, le rm se comportera de cette façon :
- si le fichier à supprimer est un dossier, il demande à l'utilisateur s'il souhaite y rentrer
-- si l'utilisateur refuse : le dossier est conservé
-- si l'utilisateur accepte, le rm demande pour chaque fichier s'il doit être supprimé
-- lorsque tous les fichiers du dossier ont été parcourus, le programme demande si le dossier doit être supprimé
--- si l'utilisateur confirme la suppression et que le dossier n'est pas vide -> erreur
--- si l'utilisateur confirme la suppression et que le dossier est vide -> suppression du dossier
--- si l'utilisateur annule la suppression et que le dossier n'est pas vide -> pas de suppression

💣️Les options --interactive (-i, -I) n'ont aucun effet si l'option --force (-f) est présente.


8.5. Demander à l'utilisateur de confirmer une seule fois la suppression des fichiers (-I)

On peut utiliser l'option -I (ou --interactive=once) qui demande une seule fois à l'utilisateur de confirmer la suppression des fichiers.

La demande de confirmation s'effectue pour deux conditions :
- dans le cas où l'un des fichiers est un dossier
- dans le cas où le nombre des fichiers à supprimer dépasse scrictement 3

Donc :
- utiliser -I pour supprimer moins de 4 fichiers seuls n'aura pas d'effets

8.5.1 Exemple : le répertoire contient un dossier et plusieurs fichiers

Dans le cas où le répertoire contient au moins un dossier, on peut utiliser conjointement les options -r et -I.

touch fichier_1 fichier_2 fichier_3 fichier_4 && mkdir dossier;
rm -rI
# RETOUR : 
# rm : supprimer 5 arguments récursivement ? y


Si l'option -r n'est pas utilisée, le rm supprimera les fichiers mais retournera une erreur :

rm: impossible de supprimer 'dossier': est un dossier


8.6. Supprimer la racine / du disque (--no-preserve-root)

Il fut un temps où il était possible d'écrire "rm -rf /"qui avait pour effet de supprimer tous les fichiers du disque.

Si l'on souhaite tout de même le faire, il faut utiliser l'option --no-preserve-root de cette façon-ci :

rm -rf --no-preserve-root /


🧙‍♂️️Note : n'ayant pas de VM sous la main pour effectuer cette commande, je vous laisse découvrir son résultat ;-).


📖️️La sécurité sur le root existerait depuis 2006 sur les OS GNU : https://en.wikipedia.org/wiki/Rm_%28Unix%29#Protection_of_the_filesystem_root.


8.7. Eviter de supprimer les dossiers montés avec mount (--preserve-root)

Depuis 2018, il est possible d'utiliser l'option --preserve-root pour éviter de supprimer certains points de montage. Si l'option est utilisée, sa valeur vaut "all" par défaut.

8.8. Afficher la version de rm (--version)

Voici un exemple de sortie :

rm --version

rm (GNU coreutils) 8.28
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Paul Rubin, David MacKenzie, Richard M. Stallman,
and Jim Meyering


💣️Ne pas utiliser -v (équivalent à --verbose) pour afficher la version de rm.


8.9. Extrait du changelog de coreutils

Bien qu'assez vieille et stable, la commande rm est de temps en temps mise à jour.
Voici un extrait du changelog de coreutils(https://fossies.org/linux/coreutils/ChangeLog):

Member "coreutils-9.0/ChangeLog" (24 Sep 2021, 328480 Bytes) of package /linux/misc/coreutils-9.0.tar.xz:

1615 2020-11-26 Nishant Nayan <nishant.nayan@oracle.com>
1616
1617 rm: do not skip files upon failure to remove an empty dir
1618 When removing a directory fails for some reason, and that directory
1619 is empty, the rm_fts code gets the return value of the excise call
1620 confused with the return value of its earlier call to prompt,
1621 causing fts_skip_tree to be called again and the next file
1622 that rm would otherwise have deleted to survive.
1623
1624 * src/remove.c (rm_fts): Ensure we only skip a single fts entry,
1625 when processing empty dirs. I.e. only skip the entry
1626 having successfully removed it.
1627 * tests/rm/empty-immutable-skip.sh: New root-only test.
1628 * tests/local.mk: Add it.
1629 * NEWS: Mention the bug fix.
1630 Fixes https://bugs.gnu.org/44883

4310 2018-06-21 Pádraig Brady <P@draigBrady.com>
4311
4312 tests: provide an option to relax the need for gdb
4313 * tests/rm/r-root.sh: gdb provides extra protection,
4314 but is not strictly necessary. So provide an option
4315 for maintainers to relax the requirements.
4316
4317 rm: add --preserve-root=all to protect mounts
4318 * src/remove.c (rm_fts): With the --preserve-root=all extension,
4319 reject command line arguments that are mount points.
4320 * src/remove.h (rm_options): Add preserve_all_root to store config.
4321 * src/mv.c (rm_option_init): Init preserve_all_root to false.
4322 * src/rm.c (main): Init preserve_all_root as per option.
4323 (usage): Describe the new option.
4324 * src/remove.c (rm_fts): Lookup the parent device id,
4325 and reject the cli argument if a separate file system.
4326 * tests/rm/one-file-system.sh: Add a test case.
4327 * NEWS: Mention the new feature.

7692 2016-10-04 Pádraig Brady <P@draigBrady.com>
7693
7694 rm: disallow --n alias for --no-preserve-root
7695 * src/rm.c (main): Ensure the full --no-preserve-root
7696 option is specified, rather than allowing --n etc.
7697 * tests/rm/r-root.sh: Add a test case.
7698 * NEWS: Mention the change in behavior.
7699
7700 Improved by Jim Meyering.
7701 Fixes http://bugs.gnu.org/24604


9. Purger les fichiers temporaires

9.1. Fichiers du journalctl

Certains fichiers comme les journaux de logs peuvent occuper une place non négligeable sur le disque.

Dans mon cas, le dossier journal a occupé jusqu'à 3.4Go avant que cela fasse partie d'un problème général (le fameux "pas assez d'espace sur ton disque de 100Go").

3,4 GiB [

9.1.0.1 ######] /var/log/journal


📖️️Les commandes proviennent du stackoverflow 130802(https://unix.stackexchange.com/a/130802).

9.1.1 Demander au service de retourner la taille utilisée

journalctl --disk-usage
Journals take up 3.4G on disk.

9.1.2 Contenu par défaut du fichier de configuration

Editer le fichier /etc/systemd/journald.conf

sudo vim /etc/systemd/journald.conf

Exemple du fichier journald.conf par défaut :

[Journal]
#Storage=auto
#Compress=yes
#Seal=yes
#SplitMode=uid
#SyncIntervalSec=5m
#RateLimitIntervalSec=30s
#RateLimitBurst=10000
#SystemMaxUse=
#SystemKeepFree=
#SystemMaxFileSize=
#SystemMaxFiles=100
#RuntimeMaxUse=
#RuntimeKeepFree=
#RuntimeMaxFileSize=
#RuntimeMaxFiles=100
#MaxRetentionSec=
#MaxFileSec=1month
#ForwardToSyslog=yes
#ForwardToKMsg=no
#ForwardToConsole=no
#ForwardToWall=yes
#TTYPath=/dev/console
#MaxLevelStore=debug
#MaxLevelSyslog=debug
#MaxLevelKMsg=notice
#MaxLevelConsole=info
#MaxLevelWall=emerg
#LineMax=48K
#ReadKMsg=yes

Par défaut, rien n'est configuré.

Modifier la ligne SystemMaxUse afin de limiter la taille utilisée sur le disque (ici 50Mo):

SystemMaxUse=50M

9.1.3 Redémarrer le service journald

sudo systemctl kill --kill-who=main --signal=SIGUSR2 systemd-journald.service
sudo systemctl restart systemd-journald.service

9.1.4 Vérification

Vérifier ensuite que cela a fonctionné :

du -sh /var/log/journal
# 49M /var/log/journal


On pourrait également faire :

journalctl --disk-usage

9.2. Fichier de cache apt

9.2.1 Afficher la taille occupée

sudo du -sh /var/cache/apt

9.2.2 Nettoyer le cache apt

#AVANT:
#sudo du -sh /var/cache/apt
#490M /var/cache/apt
sudo apt-get clean
#APRES:
#sudo du -sh /var/cache/apt
#364K /var/cache/apt

10. La commande git de A à X

10.1. Commandes de récupérations

10.1.1 Récupérer les fichiers distants

git pull --rebase

10.1.2 Afficher les branches distantes

git branch -a

10.1.3 Rafraichir la liste des branches

git fetch -p

10.2. Commandes de créations

10.2.1 Créer une nouvelle branche

git checkout -b ma_nouvelle_branche

10.2.2 Partager une nouvelle branche sur le serveur distant

git push --set-upstream origin ma_nouvelle_branche

10.2.3 Créer un nouveau commit contenant tous les fichiers modifiés

git commit -a

10.2.4 Ajouter dans le futur commit un fichier précis

git add path/to/my/file.ext

10.2.5 Créer un nouveau commit en ajoutant un commentaire et un fichier

git commit -m 'my notes' -- path/to/my/file.ext

10.2.5.1 Equivalent à ceci

git add path/to/my/file.ext
git commit -m 'my notes'

10.3. Commandes de suppression

10.3.1 Supprimer une branche locale (mode force)

git branch -D ma_branche

10.3.2 Supprimer une branche locale (mode soft)

git branch -d ma_branche

10.3.3 Supprimer la branche distante

git push origin --delete ma_branche

10.3.4 Supprimer le dernier stash

git stash drop

10.4. Autres commandes

10.4.1 Merger une branche

git checkout tronc
git merge branche

10.4.2 Mettre de coté les fichiers

git stash
git pull --rebase
git stash pop

10.4.3 Voir la liste des fichiers modifiés

git st

10.4.4 Regarder le diff d'un seul fichier avec l'état de ce dernier au précédent commit

git diff -- www/fichier

10.4.5 Partager ses commits sur le serveur distant / Pusher à tout le monde:

git push

10.4.6 Annuler dernier commit en revenant à l'état exact du commit précédent

git commit -c ORIG_HEAD

10.4.7 Annuler dernier commit sans supprimer les fichiers

git reset HEAD^

10.5. Revenir à un moment précis de l'historique GIT

git reflog -20; // affiche les 20 dernières opérations d'écriture
git checkout c2f72e1; // Revient via la signature d'une opération à ce moment précis

10.5.1 Ignorer toutes les modifications :

git reset --hard

10.5.2 Modifier le dernier commit (Attention : a faire AVANT le push)

git commit --amend fichier.txt

10.5.3 Afficher l'historique des commits

git log

10.5.4 Afficher l'historique des commits de façon plus lisible

git lg -6

10.5.5 Afficher l'historique/tous les commits qui concerne un fichier particulier

git lg --stat fichier.txt

10.6. Commandes de manipulation des tags

10.6.1 Supprimer un tag :

git tag -d v4.4.19
git push origin :v4.4.19

10.6.2 Taguer un commit particulier

git tag v4.4.19 d771f3

10.6.3 Copier les commits connus d'un point à un autre

git checkout -b nouvelle_branche
git cherry-pick commit_merdique
git checkout fichiermerdique
git commit
git cherry-pick b984e4f..ac12869

10.6.3.1 Revenir au commit - 1 (HEAD-1)

git reset HEAD^

10.6.3.2 Fusioner plusieurs commit successif meme pushés:

git rebase -i b846106
# Revenir dans le passé en ignorant toutes les modifications et les stages
git reset --hard origin/my_branch
# Récupérer un seul fichier du stash
git checkout stash@{0} -- path/to/my/file.ext

10.7. Le bisect

10.7.1 Contexte d'utilisation du bisect

Une regression est constatée en production.

Il y au moins de façon de faire :
- soit regarder le code directement ;
- soit utiliser la commande git bisect afin de trouver quel est le commit responsable de cette regression.

10.7.2 L'action git bisect run

Dans un projet critique, il est prudent d'écrire des tests de non regression. Il est possible de faire tourner ces tests via le run.

📖️️Voici un article plus complet sur le sujet : https://alanhollis.com/php/solving-bugs-phpunit-git-bisect/


Exemple :

git bisect run phpunit tests/MyClassTest.php


10.7.3 Les limites du bisect

Le bisect pré-suppose que la regression n'est liée qu'à un seul repository Git. Dans le cas où plusieurs repository sont concernés (ex: Front + Webservices), cette solution ne parait pas pertinente.
Egalement, il est difficile de confronter le temps de résolution entre les deux chemins possibles : bisect ou analyse du code directement. Le bisect s'avère peut être plus adapté s'il est trop difficile de débogguer le problème.
Eventuellement, cela peut être interessant lorsque deux personnes cherchent une solution que chacun prenne un des deux chemins.

10.8. Le cherry-pick

git cherry-pick {hash}


10.9. Le rebase interactif


Le rebase interactif permet de retravailler les commits (contenu, commentaire).
Il est très utile pour fusionner des commits répondant à la même fonctionnalité, supprimer du code qui n'a pas lieu d'être dans l'historique (ex: code de débug, sauts à la ligne, code commenté...), modifier le commentaire lié au commit.

A noter qu'il faut faire des commits dit atomiques.
- un commit qui n'introduit pas de bug dans l'application (ex: une dépendance à un commit futur) ;
- un commit qui ne concerne qu'une seule fonctionnalité.

git rebase -i {hash}


10.10. Le reflog

Le reflog est très peu connu alors que son utilité est extrèmement puissante puisqu'il est possible d'aller et venir dans le temps.
Le reflog contient toutes les commandes GIT qui ont eu un impact sur l'état du projet. Il est donc possible de revenir à un moment très précis, par exemple avant un rebase/merge.

10.10.1 Exemple d'utilisation du git reflog

git reflog -20; // affiche les 20 dernières opérations d'écriture
git checkout c2f72e1; // Revient via la signature d'une opération à ce moment précis


10.10.2 Réinitialiser une branche distante à partir d'un ancien état

git checkout branche
git reflog -30
git reset --hard {hash du commit qui fonctionnait}
git push -f


11. Stocker son identifiant et mot de passe Github


11.1. Contexte

depuis la mi 2021, Github ne permet plus aux utilisateurs d'utiliser des mots de passe personnels. A la place ce sont des tokens, plus sécurisés (sauf que comme ils sont impossibles à retenir, il faut bien les copier quelque part...alors qu'un mot de passe classique peut rester dans notre tête).

11.2. Une solution pour stocker son token github

Voici comment stocker ce token afin d'éviter de le rechercher à chaque fois :

git config --global credential.helper store


Note : il est normalement déconseillé d'utiliser le global. Mais de la même manière que le chmod 777, si c'est sur votre machine privée, cela se discute.

📖️️D'autres solutions : https://stackoverflow.com/a/67360592


12. Changer la date courante du système linux


Lancer une première fois cette commande :

timedatectl


Exemple de réponse :

    Local time: Mon 2022-05-23 10:46:39 CEST
    Universal time: Mon 2022-05-23 08:46:39 UTC
    RTC time: Mon 2022-05-23 08:46:40
    Time zone: Europe/Paris (CEST, +0200)
    System clock synchronized: yes
    NTP service: active
    RTC in local TZ: no


Si le service NTP est actif, il faut le désactiver :

sudo timedatectl set-ntp no



Changer l'heure du système :

sudo timedatectl set-time '2015-12-01'


Vérifier via date :

date

Réponse: 
Tue 01 Dec 2015 12:00:02 AM CET






13.1. Pour chiffrer un fichier

13.1.1 Avec demande de mot de passe à l'utilisateur

openssl enc -aes-256-cbc -pbkdf2  -in toto.enc  -out toto.txt


13.1.2 Avec un mot de passe en paramètre

openssl enc -aes-256-cbc -pbkdf2  -in toto.txt  -out toto.encrypted -pass pass:mot_de_passe


13.2. Pour déchiffrer un fichier

C'est la même ligne, mais avec l'option -d en plus :

openssl enc -aes-256-cbc -pbkdf2 -d -in toto.encrypted  -out toto.txt  -pass pass:mot_de_passe


13.3. Script bash

13.3.1 Script de chiffrement des fichiers d'un dossier

Le script suivant va chiffrer tous les fichiers correspondant au résultat du find (ici tous les fichiers textes du répertoire courant et de ses sous-répertoire). Le premier argument doit être le mot de passe.

#!/bin/bash

if [ -z "$1" ]
  then
    echo "Le premier argument doit etre un mot de passe"
    exit 1;
fi

for i in $(find -name \*.txt); do
    echo  "ENCRYPT: $i";
    openssl enc -aes-256-cbc -pbkdf2  -in $i -out $i.encrypted -pass pass:"$1"

    if [ $? -eq 0 ]
    then
        rm $i;
    else
        rm $i.encrypted;
    fi
done


13.3.2 Script de déchiffrement des fichiers d'un dossier

Le script suivant prend en paramètre un mot de passe et décrypte tous les fichiers préfixés par ".encrypted". Si le déchiffrement réussi, le fichier source est remplacé par le fichier décrypté.

#!/bin/bash

if [ -z "$1" ]
  then
    echo "Le premier argument doit etre un mot de passe"
    exit 1;
fi

for i in $(find -name \*.encrypted); do
    FILE_OUT_NAME="${i/\.encrypted/}"
    echo  "DECRYPT: $FILE_OUT_NAME";

    openssl enc -d -aes-256-cbc -pbkdf2  -in $i -out "$FILE_OUT_NAME" -pass pass:"$1"

    if [ $? -eq 0 ]
    then
        rm "$i";
    else
        rm "$FILE_OUT_NAME";
    fi
done


13.4. Pour ajouter une option dans Nautilus de chiffrement/déchiffrement

Nautilus est l'application par défaut dans Gnome 3 pour explorer les fichiers.
On peut ajouter des scripts bash à executer au clic droit sur un fichier.

13.4.1 Le répertoire de script

Le répertoire de script Nautilus pour l'utilisateur courant se situe ici :

~/.local/share/nautilus/scripts

Par défaut, le dossier est vide.

13.4.2 Premier pas dans la création d'un script Nautilus

Créer un fichier script pour Nautilus est assez facile, voici un exemple très simple qui va enregistrer le nom du fichier sélectionné par le clic droit dans un fichier temporaire.

13.4.2.1 Création et enregistrement du fichier

Créer ce fichier et l'enregistrer dans ~/.local/share/nautilus/scripts/test.sh

#!/bin/bash

CURRENT_DIR=`pwd`
CURRENT_FILE_PATH="${CURRENT_DIR}/${1}"
echo $CURRENT_FILE_PATH > /tmp/nautilus_script.txt


Puis ajouter les droits d'execution sur le fichier :

chmod +x ~/.local/share/nautilus/scripts/test.sh


Puis, ouvrir Nautilus, faire un clic droit sur le fichier, sélectionner Scripts > test.sh.

Constater que le fichier /tmp/nautilus_script.txt a bien été créé.


13.4.3 Création des scripts de chiffrement et de déchiffrement

Créer les deux fichiers "encrypt" et "decrypt" avec le contenu suivant :

~/.local/share/nautilus/scripts/encrypt

#!/bin/bash

CURRENT_DIR=`pwd`
CURRENT_FILE_PATH="${CURRENT_DIR}/${1}"

if [[ -f $CURRENT_FILE_PATH ]]
then

    ENCRYPTED_FILE_PATH="${CURRENT_FILE_PATH}.encrypted"
    ENCRYPTION_PASSWORD="SECRET"

    openssl enc -aes-256-cbc -pbkdf2  -in "${CURRENT_FILE_PATH}" -out "${ENCRYPTED_FILE_PATH}" -pass pass:$ENCRYPTION_PASSWORD

    if [ $? -eq 0 ]
    then
      rm "${CURRENT_FILE_PATH}"
    else
      rm "${ENCRYPTED_FILE_PATH}";
    fi
fi


~/.local/share/nautilus/scripts/decrypt

#!/bin/bash

CURRENT_DIR=`pwd`
CURRENT_FILE_PATH="${CURRENT_DIR}/${1}"

if [[ -f $CURRENT_FILE_PATH ]]
then
FILE_OUT_NAME="${CURRENT_FILE_PATH/\.encrypted/}"
ENCRYPTION_PASSWORD="SECRET"

    openssl enc -d -aes-256-cbc -pbkdf2  -in "${CURRENT_FILE_PATH}" -out "${FILE_OUT_NAME}" -pass pass:$ENCRYPTION_PASSWORD

    if [ $? -eq 0 ]
    then
        rm "${CURRENT_FILE_PATH}"
    else
        rm "${FILE_OUT_NAME}";
    fi
fi


Enfin, mettre les droits d'execution sur les deux fichiers "encrypt" et "decrypt" :

chmod +x ~/.local/share/nautilus/scripts/decrypt ~/.local/share/nautilus/scripts/encrypt


Voilà, vous avez maintenant deux raccourcis rapides dans Nautilus pour chiffrer et déchiffrer un fichier.

💣️Limite: la valeur de la variable $ENCRYPTION_PASSWORD est facilement trouvable.


💣️Ce script ne fonctionne que pour des fichiers simples, pas avec des dossiers.


💡️Pour chiffrer un dossier, l'astuce est de le zipper avant de le chiffrer.
Voir ici : https://www.reddit.com/r/crypto/comments/4g5i9q/how_do_i_use_openssl_to_encrypt_a_folder/

14. Cloner des disques ou des clefs USB


14.1. Cloner un disque

14.1.1 Lister les disques durs

Qu'ils soient internes ou externes :

sudo lshw -class disk

Valeurs de class possibles :
bridge, bus, cpu, disk, display, generic, input, memory, multimedia, network, power, processor, storage, system, volume.

14.1.2 Déplacer les fichiers lourds sur un disque (facultatif)

Il peut être pertinent d'enlever les fichiers lourds qui n'ont pas besoin d'être copiés (ex: les films).
On peut les retrouver de cette façon-ci (se déplacer dans le répertoire du disque dur /media/xxx avant).

sudo find . -type f -size +500000k -printf "%p %s\n"


14.1.3 Cloner le disque vers un fichier .iso

sudo dd if=/dev/sdc of="/tmp/out.iso" bs=100M


Remplacer "sdc" selon le retour de lshw (attention car la lettre peut changer d'un branchement à l'autre).


14.2. Créer une clef bootable Windows

14.2.1 Lister les clefs USB connectées

sudo blkid


14.2.2 Formatter la clef USB

Utiliser l’utilitaire Disques dans Ubuntu.

14.2.3 Créer le boot

sudo dd bs=4M if=/tmp/windows.iso of=/dev/sdd1 conv=fdatasync  status=progress


Remplacer "sdd1" selon le retour de blkid ou de l'utilitaire Disques.

15. Comparer les lignes de deux fichiers textes


15.1. Afficher les lignes communes entre deux fichiers

Supposons deux fichiers file_1 et file_2 composés de plusieurs lignes triées par sort.

Pour afficher les lignes communes entre les deux fichiers, on peut écrire ceci :

comm -1 -2 /tmp/file_1.txt /tmp/file_2.txt



16. Compression chiffrée de fichiers


16.1. Compresser un fichier avec un mot de passe

tar cz folder_to_encrypt | openssl enc -aes-256-cbc -e > out.tar.gz.enc


openssl demande alors de taper un mot de passe (deux fois).

16.1.1 Créer une archive chiffrée depuis les résultats d'un find

Il est possible de filtrer les fichiers que l'on souhaite zipper en redirigeant la sortie de find vers tar.

Par exemple, la ligne suivante va archiver uniquement les fichiers qui ont été créés depuis moins de 2 heures (120 minutes) :

find .  -cmin -120 |  tar cz  --files-from=- | openssl enc -aes-256-cbc -e > out.tar.gz.enc


On peut également utiliser cette syntaxe, plus simple :
Ici on compresserait tous les fichiers créés entre deux dates.

find .  -newermt "2021-12-06 16:20" ! -newermt "2021-12-07" |  tar cz  --files-from=- | openssl enc -aes-256-cbc -e > out.tar.gz.enc


📖️️Aller + loin avec le find sur cette page https://aide-memoire.blog-machine.info/linux-trouver-tous-les-fichiers-modifies-a-une-date-precise-et-meme-a-une-heure-precise/


16.2. Décompresser un fichier avec un mot de passe

16.2.1 Première façon :

openssl enc -aes-256-cbc -d -in out.tar.gz.enc | tar xz


16.2.2 Deuxième façon :

gpg --encrypt out.tar.gz


📖️️Source : https://superuser.com/a/162628

17. Wifi


17.1. Forcer la fréquence d'une connexion

Dans le cas où il vaut mieux empecher Ubuntu de choisir lui même la fréquence Wifi 2Ghz ou 5,Ghz.
Lancer la commande suivante :

sudo nm-connection-editor

Sélectionner sa connexion et cliquer sur le bouton de réglage.
Modifier le paramètre Onglet Wifi > Bande.
Puis rémarrer le network-manager :

sudo service network-manager restart



17.2. Afficher le nom de la carte WIFi

Pour afficher les cartes réseaux du PC, on peut utiliser la commande suivante:

sudo lshw -C network

Ceci affiche par exemple :

*-network
    description: Interface réseau sans fil
    produit: Wireless 7260
    fabricant: Intel Corporation
    identifiant matériel: 0
    information bus: pci@0000:04:00.0
    nom logique: wlan0
    version: bb
    numéro de série: 58:91:cf:76:9e:f9
    bits: 64 bits
    horloge: 33MHz
    fonctionnalités: pm msi pciexpress bus_master cap_list ethernet physical wireless
    configuration : broadcast=yes driver=iwlwifi driverversion=5.4.0-90-generic firmware=17.3216344376.0 ip=192.168.1.111 latency=0 link=yes multicast=yes wireless=IEEE 802.11
    ressources : irq:33 mémoire:f1800000-f1801fff


17.3. Correspondances entre les versions de Wifi

💡️Voir ici


📖️️Aller plus loin : https://fr.wikipedia.org/wiki/Wi-Fi

18. Les binaires de bases

💣️Cette page sera bientôt supprimée.


18.1. Contexte

Par défaut, Debian embarque plusieurs binaires vitaux que l'on peut trouver dans le dossier /bin.

En voici une liste qui doit être peu ou prou la même depuis des années (`ls /bin`):

bash bzip2recover date egrep hciconfig lowntfs-3g mv ntfscmp pax red ss systemd-notify uncompress zgrep
brltty bzless dd false hostname ls nano ntfsfallocate paxcpio rm static-sh systemd-sysusers unicode_start zless
bunzip2 bzmore df fgconsole ip lsblk nc ntfsfix paxtar rmdir stty systemd-tmpfiles vdir zmore
busybox cat dir fgrep journalctl lsmod nc.openbsd ntfsinfo pidof rnano su systemd-tty-ask-password-agent wdctl znew
bzcat chacl dmesg findmnt kbd_mode mkdir netcat ntfsls ping run-parts sync tar which
bzcmp chgrp dnsdomainname fuser keyctl mknod netstat ntfsmove ping4 sed systemctl tempfile ypdomainname
bzdiff chmod domainname fusermount kill mktemp networkctl ntfsrecover ping6 setfacl systemd touch zcat
bzegrep chown dumpkeys getfacl kmod more nisdomainname ntfssecaudit plymouth setfont systemd-ask-password true zcmp
bzexe chvt echo grep ln mount ntfs-3g ntfstruncate ps setupcon systemd-escape udevadm zdiff
bzfgrep cp ed gunzip loadkeys mountpoint ntfs-3g.probe ntfsusermap pwd sh systemd-hwdb ulockmgr_server zegrep
bzgrep cpio efibootdump gzexe login mt ntfscat ntfswipe rbash sh.distrib systemd-inhibit umount zfgrep
bzip2 dash efibootmgr gzip loginctl mt-gnu ntfscluster openvt readlink sleep systemd-machine-id-setup uname zforce


18.2. La commande rm

Le rm ("remove") est une commande vitale qui permet de supprimer un fichier (ou un dossier) :

18.2.1 Pour supprimer un fichier

rm fichier.txt


18.2.2 Pour supprimer un dossier

On peut procéder de deux manières :

rm -r dossier
# Notes: 
# On peut également utiliser -R ou --recursive à la place de -r


rmdir dossier


18.2.2.1 Astuce : supprimer un dossier contenant trop d'éléments :

Parfois, le rm ne fonctionne pas car l'ensemble des fichiers du répertoires semble être scanné avant d'être effacé.

On peut alors procédé de cette façon là :

cd dossier;
ls | xargs rm -f


Équivaut à

cd dossier;
ls | while read param
do
rm -f param
done


18.3. La commande grep

Le grep (globally search for a regular expression and print matching lines) permet de trouver les lignes qui correspondent à une expression régulière

18.3.1 Exemple : parser une page web et garder tous les URLs qui ont un certain pattern

Prenons ce cas :
- une page web contient plusieurs liens <a href=""> qui nous intéressent
- on souhaite la scanner et récupérer l'ensemble de ces liens et les dédoublonner

GET https://www.domain.com/xxx/aaa | grep -aoP "/xxx/[\-a-z0-9]+.html" | sort | uniq

# Résultat :
# …
# /xxx/aaa-111.html
# /xxx/bbb-222.html
# /xxx/ccc-333.html


18.4. La commande head

Le head affiche par défaut les 10 premières lignes d’un fichier

18.4.1 Exemple du head pour afficher les 20 premières lignes d'un fichier

head -20 /tmp/fichier.txt


18.5. La commande tail

Le head affiche par défaut les 10 dernière lignes d’un fichier

18.5.1 Exemple du tail pour afficher les 20 dernières lignes d'un fichier

tail -20 /tmp/fichier.txt


18.5.2 Afficher en contenu les changements dans un fichier

Le tail -f permet de d’afficher en temps réel les dernières lignes ajoutées à un fichier (par exemple un log de serveur web).

tail -f access.log


19. Copier des fichiers


19.1. Copier des fichiers en conservant les liens symboliques avec -P

Pour conserver les liens symboliques, il faut utiliser l'option -P (ou --no-dereference) de la commande cp.

Voici un exemple de copie d'un dossier :

cp -RP source_directory target_directory

20. Installer jellyfin


20.1. Contexte

jellyfin est une application de type media center.
jellyfin est composé de deux parties :
* la partie serveur qui scanne les différents films et convertie à la volée ceux-ci
* la partie web qui est accessible via un navigateur classique, ainsi que via des applications mobiles

20.2. Pourquoi pas Kodi ?

Kodi est, en 2021, la solution la plus populaire.
Elle est très complète, voire trop complète : trop de menus, trop de boutons, trop d'extensions : on s'y perd. A vouloir répondre à tous les besoins, on s'écarte parfois du besoin principal : afficher de manière sexy un dossier de films *.avi/*.mp3/*.mkv.

Jellyfin est un projet un peu plus récent, et donc moins "enrichi", donc plus léger, plus simple.

Par contre, à la différence d'un Kodi où le lecteur vidéo lit à la demande le fichier, dans le cas de Jellyfin, une conversion du fichier est faite via ffmpeg. Ainsi, regarder un film sur le même ordinateur que le serveur sera très gourmand en ressource (lecture + encodage + streaming). Pour pallier à ça, il faut installer un client player. Disons que Kodi embarque une solution tout en un et Jellyfin sépare la partie serveur (traitement de la base de données) de la partie cliente (affichage de la vidéo), ce qui n'est pas plus mal.

20.3. Modifier le chemin des dossiers de métadonnées et de cache

Dans le cas où l'on ne souhaite pas que jellyfin stocke ses données sur le disque principal (dans le cas où une partition est dédiée au stockage et une autre à l'OS par exemple). On peut modifier le chemin des répertoires utilisés.

Il faudra pour cela se rendre dans le dashboard via l'interface web (il est surement possible de le faire autrement mais peut être que c'est une configuration stockée dans une base SQLite) et créer en parallèle les dossiers "metadata" et "cache" avec les bons droits.

20.3.1 Modifier la configuration sur la page d'administration

Administrateur > Tableau de bord (ou Dashboard)

http://localhost:8096/web/index.html#!/dashboardgeneral.html


Modifier dans la section Serveur > Général
La partie Chemin

Chemin du cache :
/mnt/fc14d9a6-2ec9-4f9a-ac24-a7995f271517/jellyfin/cache

Chemin des métadonnées :
/mnt/fc14d9a6-2ec9-4f9a-ac24-a7995f271517/jellyfin/metadata


Modifier dans la section Serveur > Lecture
La partie du transcodeur

Emplacement du transcodeur :
/mnt/fc14d9a6-2ec9-4f9a-ac24-a7995f271517/jellyfin/transcodes


20.3.2 Créer le dossier jellyfin sur le disque de stockage

sudo mkdir /mnt/fc14d9a6-2ec9-4f9a-ac24-a7995f271517/jellyfin


20.3.3 Copier les métadonnées des films

sudo cp -r /var/lib/jellyfin/metadata /mnt/fc14d9a6-2ec9-4f9a-ac24-a7995f271517/jellyfin/metadata


20.3.4 Copier le dossier de cache

sudo cp -r /var/cache/jellyfin /mnt/fc14d9a6-2ec9-4f9a-ac24-a7995f271517/jellyfin/cache


20.3.5 Créer le dossier transcoders

mkdir /mnt/fc14d9a6-2ec9-4f9a-ac24-a7995f271517/jellyfin/transcodes


20.3.6 Il faudrait plutot faire un chown pour l'utilisateur jellyfin (celui du service)

sudo chmod -R 777 /mnt/fc14d9a6-2ec9-4f9a-ac24-a7995f271517/jellyfin


20.3.7 Redémarrer le service

Peut être que cette action est inutile, mais on sera sûr que le service utilisera les nouveaux dossiers.

sudo service jellyfin restart


20.3.8 Relancer une actualisation

Ensuite, je conseille de lancer une actualisation de la médiathèque (surtout dans le cas où vous constatez un problème dans les onglets Collections / Genre).

Pour ce faire, toujours dans le tableau de bord d'administration, section Serveur :

bouton : Actualiser toutes les médiathèques


20.4. Installer le client video

L'avantage du client vidéo est qu'il dispose de + de codecs que les navigateurs web. Donc la partie transcodage est évitée, et donc non seulement le serveur est soulagée, mais le contenu arrive forcément plus vite.

Récuperer le .deb depuis la page des releases :
https://github.com/jellyfin/jellyfin-media-player/releases

Ensuite, en ligne de commande, faire ceci (adapter en fonction du fichier)

sudo dpkg -i jellyfin-media-player_1.6.1-1_amd64-buster.deb


20.4.1 Résoudre un probleme d'installation

pour ma part, j'ai eu cette erreur

Sélection du paquet jellyfin-media-player précédemment désélectionné.
(Lecture de la base de données... 319213 fichiers et répertoires déjà installés.)
Préparation du dépaquetage de jellyfin-media-player_1.6.1-1_amd64-buster.deb ...
Dépaquetage de jellyfin-media-player (1.6.1-1) ...
dpkg: des problèmes de dépendances empêchent la configuration de jellyfin-media-player :
jellyfin-media-player dépend de libmpv1 ; cependant :
Le paquet libmpv1 n'est pas installé.
jellyfin-media-player dépend de libqt5webengine5 ; cependant :
Le paquet libqt5webengine5 n'est pas installé.
jellyfin-media-player dépend de libcec4 | libcec6 ; cependant :
Le paquet libcec4 n'est pas installé.
Le paquet libcec6 n'est pas installé.
jellyfin-media-player dépend de qml-module-qtwebengine ; cependant :
Le paquet qml-module-qtwebengine n'est pas installé.
jellyfin-media-player dépend de qml-module-qtwebchannel ; cependant :
Le paquet qml-module-qtwebchannel n'est pas installé.


Qui a été résolue un peu magiquement via ces deux commandes :

sudo apt install libmpv1
sudo apt --fix-broken install


21. Lancer un programme en arrière plan


Contexte : vous souhaitez lancer un programme sur un linux distant (serveur privé / VM) accessible depuis SSH. Or, vous savez que ce programme peut prendre un peu de temps pour s'executer et vous souhaitez vous déconnecter du serveur, soit parce que vous devez éteindre l'ordinateur Client, soit parce que votre connexion internet est instable.

La solution à ce problème est de faire tourner l'application en tâche de fond. C'est déjà ce qu'il se passe pour beaucoup d'application (tout ce qui tourne à travers les services par exemple).

21.1. L'application screen

screen permet de créer une session qui peut être partagé d'un accès SSH à un autre.

21.1.1 Lancer screen

Executer cette commande :

21.1.1.1 Créer une nouvelle session

screen -S nom_de_la_session


21.1.1.2 Lancer une commande

Ceci ouvre une nouvelle session, on peut y taper une commande synchrone ou non :

sleep 4555555;


21.1.1.3 Quitter la session

Il y a plusieurs moyens de quitter la session dont voici les deux principaux :
- cltr+a suivi de la touche d
- fermer le terminal

21.1.1.4 Afficher les sessions screen

Lancer cette commande pour voir tous les screens :

screen -list


21.1.1.5 Revenir à la session

Se reconnecter au serveur, puis taper cette commande

screen -r nom_de_la_session


📖️️Source: https://doc.ubuntu-fr.org/screen


21.2. L'application nohup

Le nohup combiné au symbole & permet d'avoir un effet similaire : une tâche est executée en arrière plan indépendante de la déconnexion de l'utilisateur. Elle continuera de tourner même si l'utilisateur se déconnecte, volontairement ou non.

Le problème étant que si la tâche lancée fait un stdout pendant son traitement (à travers plusieurs echo par exemple), on ne peut plus les voir simplement (il faut alors redigirer cette sortie sur un fichier afin d'y avoir accès). En utilisant screen, on évite de faire une redirection dans un fichier car on peut revenir "dans l'execution" de la tâche.

nohup mon_programme &








Commencer par démonter si besoin le montage existant :
umount /media/denis/Films

Puis créer un dossier vide :
mkdir /media/wd_elements

Et enfin faire cette commande :
sudo mount -t ntfs /dev/sdc1 /media/wd_elements

23. Rechercher des fichiers volumineux sous Debian


23.1. Rechercher les fichiers volumineux

find . -type f -size +200M -printf "%p %s\n"


Remplacer M par k pour une recherche en ko.

23.2. Rechercher les dossiers les plus volumineux

du -hms /home/user/* | sort -nr | head


23.3. Explorer les dossiers et fichiers volumineux

Le programme ncdu est très pratique pour cela.

Ce n'est pas un binaire natif donc il faut l'installer avec un apt install.

ncdu /

24. Récupérer les données effacées d'un disque dur


24.1. Récupérer les données

Utiliser photorec

24.2. Regrouper les fichiers par extensions

Télécharger et executer ce script bash : https://github.com/erikpopp/sort-photorec/blob/master/sort-photorec

chmod +x sort-photorec.sh
./sort-photorec.sh /tmp/photorec-disk


24.3. Supprimer les fichiers en doublon


Lancer le script suivant (le code est d'assez mauvaise qualité mais ce n'est pas grave).
Par défaut, le script n'efface aucun fichier, il faut saisir l'option no-dry pour le faire.
Le premier argument est le nom du dossier à scanner. La recherche est récursive.

php dedoublonne.php /tmp --no-dry


<?php

if (!isset($argv[1])) {
    exit('Dir argument is needed'.PHP_EOL);
}

$testMode = true;
foreach($argv as $argument) {
    if ($argument === '--no-dry') {
        $testMode = false;
    }
}

$dirPath = $argv[1];

if (!is_dir($dirPath)) {
    exit('The dir does not exist'.PHP_EOL);
}

$dirPath = preg_replace('#/?$#', '', $dirPath);


function findFiles($dirPath)
{
    $files = array();

    foreach(scandir($dirPath) as $currentFileName) {
        if ($currentFileName == '.' || $currentFileName == '..') {
            continue;
        }

        $currentFilePath = sprintf('%s/%s', $dirPath, $currentFileName);

        if (is_dir($currentFilePath)) {
            $files = array_merge(findFiles($currentFilePath), $files);
        } else {

            $currentFileSize = filesize($currentFilePath);

            $files[] = [
                'path' => $currentFilePath,
                'name' => $currentFileName,
                'size' => $currentFileSize,
            ];
        }
    }

    return $files;
}
$files = findFiles($dirPath);



usort($files, function($fileA, $fileB) {
    return $fileA['size'] >= $fileB['size'];
});


$lastFile = null;
$filePoolBySize = array();
$lastFileSize = null;

$nbDeletedFiles = 0;
foreach ($files as $currentFile) {
    $currentFileIsDeleted = false;

    if (!empty($filePoolBySize[$currentFile['size']])) {
        foreach ($filePoolBySize[$currentFile['size']] as $lastFile) {
            $currentHash  = hash_file('md5', $currentFile['path']);
            $lastHash  = hash_file('md5', $lastFile['path']);
            if ($currentHash === $lastHash) {
                if ($testMode === false) {
                    unlink($currentFile['path']);
                }

                $nbDeletedFiles++;
                $currentFileIsDeleted = true;
                echo sprintf('Suppression doublon "%s"'.PHP_EOL, $currentFile['path']);
                break;
            }
        }
    }

    if ($currentFileIsDeleted == false) {
        $filePoolBySize[$currentFile['size']][] = $currentFile;
        $lastFile = $currentFile;
    }
}

echo sprintf('Nb fichiers : %s'.PHP_EOL, count($files));
echo sprintf('Nb doublons supprimés : %s'.PHP_EOL, $nbDeletedFiles);
echo sprintf('Nb fichiers restants : %s'.PHP_EOL, count($files) - $nbDeletedFiles);

25. Terminal

Voici quelques astuces à utiliser dans le terminal.

25.1. Les raccourcis dans le terminal

25.1.1 Ctrl + A et Ctrl + E

* Le ctrl + A permet de placer le curseur au début de la ligne du terminal.
* Le ctrl + E permet de placer le curseur à la fin de la ligne du terminal.

25.1.2 Ctrl + R

Cette action permet de faire une recherche dans l'historique et également de positionner le pointeur de la ligne courante en fonction des caractères écrits.

25.1.3 Touche Echap suivie de la touche dièse

Cet enchainement Echap+# permet de mettre en commentaire la ligne courante du terminal.
L'interêt derrière cette action est de placer la commande courante dans l'historique du terminal afin de la retrouver plus tard via un Cltr+R.

25.1.4 Ctrl + W

Cette action permet d'effacer le bout de texte se trouvant avant le curseur.

25.1.5 Tabulation

La tabulation permet d'autocompléter un début de commande (ou un début de nom de fichier).

📖️️Plus d’astuces : https://www.youtube.com/watch?v=MAPbo8cNkJ8


25.2. Les symboles

25.2.1 Les chevrons > >>

25.2.1.1 Rediriger le stdout dans un fichier

echo "texte" > /tmp/stdout.txt; 
#équivaut à echo "texte" 1> /tmp/stdout.txt


25.2.1.2 Rediriger le stderr dans un fichier :

cat file_not_found 2> /tmp/stderr.txt


25.2.1.3 Séparer les deux flux stdout et stderr:

cat file_not_found 1> /tmp/stdout.txt 2> /tmp/stderr.txt


25.2.1.4 Rediriger les deux flux stdout et stderr dans un même fichier

cat file_not_found &> /tmp/stdouterr.txt


25.2.2 Les pipes |

Les pipes permettent de transmettre les flux de processus en processus

25.2.2.1 Compter le nombre de GET au serveur web

cat project_access.log | grep GET | wc -l

# Notes: 
# cat project_access.log : affiche sur le stdout le contenu du fichier
# grep GET : filtre chaque ligne du stdout pour n’afficher que celles contenant le mot GET
# wc -l : compte le nombre de lignes retournées


25.2.2.2 Utiliser à la fois les pipes et les chevrons

cat project_access.log | grep GET > GET.log


25.2.3 Les "et commerciaux" &&

Les && permettent de chaîner les processus, chaque processus se base sur le statut du précédent

25.2.4 Exemple d'utilisation des &&

Ex : exécute le cache clear uniquement si le composer marche

composer install && bin/console cache:clear

Ici la deuxième partie du script s'execute uniquement si la première fonctionne.

25.2.4.1 Exemple

Afficher le message "Fichier trouvé" uniquement si le fichier existe.

ls /tmp &>/dev/null && echo "Fichier trouvé";

# Note: 
# Le /dev/null permet de masquer visuellement les flux stdout/stderr du ls.


25.2.5 La variable spéciale $?

la variable $? contient le statut de la dernière commande et prend deux valeurs.

25.2.5.1 Code retour OK : valeur 0

0 : quand la dernière commande est réussie.

echo “coucou”;
echo $?; // affiche 0


25.2.5.2 Code retour KO : valeur 1

1 : quand la dernière commande est en erreur.

cat file_not_found;
echo $?; // affiche 1



25.2.6 Le point virgule ;

Les ; séparent les processus qui s'exécutent séquentiellement sans prendre en compte l’état de leurs prédécesseurs;

echo coucou; cat file_not_found &>/dev/null; echo yahou;

# Résultat : les deux messages s'affichent malgré la commande du milieu qui a échoué.
>coucou
>yahou


A comparer avec cet exemple qui utilise les "et commerciaux" & :

echo coucou && cat file_not_found &>/dev/null && echo yahou;

# Résultat : Seul le premier message s'affiche car le programme s’arrête à cause du cat qui plante.
>coucou



26. Qu'est ce qu'un site web ?

26.1. Nécessité du réseau entre ordinateurs

Il faut revenir aux fondamentaux.

Le rôle d'un ordinateur n'est rien d'autre que de traiter de l'information.
Cette information est enregistrée sous la forme de 0 et de 1 sur des objets physiques (disques durs, bandes magnétiques...).

Très vite, on avait le besoin que les ordinateurs s'échangent des données plus simplement que de devoir déplacer les cartes mémoires d'un poste à l'autre.

C'est ainsi que l'on a relié les ordinateurs ensemble avec des cables électriques pour qu'ils puissent s'échanger des données.
C'est plus ou moins comme cela que l'on schématise Internet : ce sont plein d'ordinateurs reliés ensemble avec des cables.

26.2. Prémices du web

Un jour, les hommes se sont dits que ce serait tout de même chouette si on pouvait afficher les documents d'un ordinateur de façon lisible.
C'est comme ça que le web et que le premier site web est né : l'ingénieur a réussi a afficher le contenu d'un fichier texte sur un ordinateur différent que celui qu'il utilisait.
Les bases sont posées, il ne reste plus qu'à définir tout cela.

* Le serveur : c'est l'ordinateur qui met à disposition les données
* Le client : c'est l'ordinateur qui affiche les données à l'écran

Sur l'ordinateur Client, on utilise un logiciel qui s'appelle un navigateur, il en existe quelques uns dont les plus connus sont sans doute encore aujourd'hui "Google Chrome" (que l'on appelle vulgairement Chrome et non Google car cela désigne déjà le nom du moteur de recherche), "Firefox Mozilla", "Microsoft Edge", "Internet Explorer", "Safari" (commun sur les ordinateurs d'Apple), puis beaucoup d'autres encore comme Opera, Brave...

A ce navigateur, on lui indique l'adresse d'un ordinateur Serveur, cette adresse a le même objectif qu'une adresse postale : localiser un ordinateur sur la carte.
Tous les ordinateurs branchés à Internet ont donc une adresse internet et on les identifie grâce à celle-ci.

Cette adresse est nommée "adresse IP" où le I veut dire Internet et le P veut dire Protocol.

Là où cela devient compliqué pour les hommes, c'est qu'on ne va pas commencé à retenir des adresses IP pour accéder à tel ou tel ordinateur comme on pouvait d'ailleurs le faire avec les numéros de téléphone dans le passé.
Concernant les numéros de téléphone, on utilise des répertoires de noms, à "Maman", tel numéro de téléphone, à "Police", un autre etc...

Pour les adresses des ordinateurs, c'est peu ou prou la même chose sauf que le répertoire est écrits par d'autres personnes que nous.
Ainsi, pour discuter avec le serveur français de Google qui a l'adresse IP 172.217.171.195, on passera par un répertoire général et on lui indiquera google.fr, c'est autrement plus simple.

Nous utilisons donc des noms pour cibler des ordinateurs sur le réseau Internet.

26.3. L'URL, l'adresse des ordinateurs sur le réseau Internet

Un navigateur est spécialisé avant tout pour récupérer des documents web auprès des ordinateurs serveurs. On lui indique l'adresse de ces ordinateurs avec ce que l'on nomme une URL.

Cette URL (ex: https://www.google.fr?q=toto) peut être décomposée en plusieurs bouts :
* https : indique le langage que devront utiliser le navigateur et le serveur pour communiquer ensemble (nous n'avons pas à nous en mêler)
* www : c'est un sous domaine qui veut dire world wide web, ce sous domaine est parfois utilisé et parfois non pour accéder à du contenu web
* google.fr : c'est le nom du domaine
* ?q=toto : c'est une façon de demander au site web des données précises

Lorsqu'on tape cette URL dans la barre d'adresse du navigateur, notre ordinateur communique alors avec un à plusieurs ordinateurs serveurs via le navigateur (c'est transparent pour l'utilisateur).
Ces ordinateurs serveurs vont alors lui retourner la plupart du temps une page, appelée page web.

26.4. Définition d'une page web

Une page web n'est rien d'autre qu'un fichier texte qu'un ordinateur serveur transmet à notre ordinateur client.

26.4.1 Exemple de page de type texte

Rien n'empeche à l'ordinateur serveur de vous afficher le fichier bonjour.html dont le contenu est le suivant :

ceci est une page web


Votre navigateur affichera alors dans ce cas le texte "ceci est une page web".

Je vous vois venir : je n'ai jamais vu une page web comme ça, sur une page web, il y a des couleurs, des images, du texte souligné, barré, gras, italique etc...

Et là je vous répond : effectivement, vous avez l'oeil ! Mais maintenant si je vous questionne à mon tour en vous demandant "Pourquoi et comment ?"

26.5. Pourquoi une page web c'est plus beau qu'un texte en noir et blanc ?

Et bien, d'une part pour se démarquer des autres pages et d'autres parts, il est évident que la page web a une portée plus subliminale avec des images/couleurs et autres animations qu'une simple page en noir et blanc.
Et donc, on peut vous vendre des produits.

27. Correspondances entre les différences versions WIFI

Voici la liste des principales normes Wifi et leur nom commercial, qui est plus pratique pour s'y retrouver :

802.11b  - Wifi 1 - 2,4 Ghz          - 2000
802.11a  - Wifi 2 - 5   Ghz          - 1999
802.11g  - Wifi 3 - 2,4 Ghz          - 2003
802.11n  - Wifi 4 - 2,4 Ghz et 5 Ghz - 2006
802.11ac - Wifi 5 - 2,4 Ghz et 5 Ghz - 2014
802.11ax - Wifi 6 - 2,4 Ghz et 5 Ghz - 2021
802.11be - Wifi 7

28. L'IP locale sous toutes ses formes


Il existe plusieurs moyens de noter une IP locale :

localhost
127.0.0.1
127.1
2130706433
0x7f000001
0x7f.0.0.1


28.1. La valeur 2130706433

La valeur 2130706433 est étonnante au premier abord mais ce n'est rien d'autre que la notation en base 10 (ie. nombre entier) de l'IP locale.

Une IP V4 classique va de 0.0.0.0 à 255.255.255.255, ce qui fait 4294967296 combinaisons (256^4).

Démonstration :

127.0.0.1 = 127*(256*256*256)+0*(256*256)+0*(256)+1 = 2130706433


28.2. La valeur 0x7f000001

La valeur 0x7f000001 peut s'écrire 7F 00 00 01, 7F étant la représentation hexadécimale de 127.

28.3. Quasiment toutes les 127.*.*.* sont locales.

Que ce soit 127.1.1.1, 127.255.1.112, ou quasiment tout ce qui commence par "127." pointe vers le localhost.

Seules certaines valeurs comme 127.0.0.0 ou 127.255.255.255 ne fonctionneront pas.

29. Le nitpicking


Parfois, on peut croiser le mot "nit" dans les pull requests de github.

Le mot "nit" est l'équivalent anglais du mot "lente".
La lente est l'oeuf du pou.
"Faire du nitpicking" revient donc à l'expression française "Chercher des poux dans la tête de quelqu'un".

Dans le cas d'une revue de code, on peut appeler nitpicker quelqu'un qui fait des commentaires sur des lignes non forcément pertinentes dans le fond (ex: relever une faute d'orthographe dans un commentaire).

Lorsque le relecteur pense lui même que son retour ne changera pas la face du monde, il peut commencer son commentaire par le mot "nit:". Ainsi, il est plus facile pour le codeur de filtrer les "bons retours" (ie. ceux sur le fond) des mauvais.



📖️️Source : https://en.wikipedia.org/wiki/Nitpicking


30. Pourquoi du private, du protected et du public ?

30.1. Les différences entre les visibilités

Alors, déjà il faut bien comprendre la différence entre les trois :
* lorsqu'un attribut ou une méthode d'une classe est private, seule l'instance de la classe peut y accéder ;
* lorsqu'un attribut ou une méthode d'une classe est protected, seules les instances de la classe ou des classes qui en héritent peuvent y accéder ;
* lorsqu'un attribut ou une méthode d'une classe est public, tout le monde peut y accéder, mère fille tonton tata... ;

30.2. En pratique : dans le doute, tout mettre en visibilité private.

En sécurité, on a une petite phrase qui revient assez souvent qui est "On interdit par défaut, et on autorise au cas par cas".

Et bien, dans une classe PHP, on peut tout à fait appliquer le même principe : tout déclaré privée et dans le besoin augmenter la visibilité.

30.2.1 Pourquoi pas tout en public ?

Lorsque tout est en public, on ne se pose pas vraiment de question, mais on ne contrôle plus très bien les choses.

Par exemple : si les attributs sont en visibilité public, rien n'empêche un autre développeur de mettre à jour leurs valeurs directement.

Mais si vous souhaitez faire autre chose au moment de l'affectation d'un attribut, par exemple contrôler que la nouvelle valeur corresponde aux règles métiers ? Et bien cela devient compliquer car il faudra repasser à tous les endroits où l'attribut est modifié pour ajouter le contrôle. Pire : si la modification de l'attribut passe par une méthode magique, cela devient très compliqué.

Si les attributs sont par défaut en visibilité private, vous maitrisez leurs changements de valeur via des méthodes qui sont publiques de type setAttribute($value).

C'est la même chose pour les méthodes, si elles sont en visibilité private par défaut, c'est lorsqu'on n'en aura besoin dans une autre classe que le besoin de la passer en public sera "peut-être" légitimé.

Bien sûr, avec le temps, on finit par mettre certaines méthodes directement en public, par exemple ce qu'on nomme les setters et les getters.
Mais pour d'autres méthodes, cela est plus prudent de les passer en visibilité private.

30.2.2 Et le protected ?

Quant au protected, cela est pratique d'y penser dès que l'héritage s'avère nécessaire (ex, on souhaite enrichir une librairie avec notre propre code).

Deux cas de figure se présentent alors :
* si la classe que vous créez a pour vocation d'être partagée avec d'autres membres de votre équipe de développement, il faudra bien réfléchir à la visibilité de chaque méthode (au pire, une Merge Request du développeur concerné vous demandera de faire une modification).
* si la classe que vous créez a pour vocation de rester dans votre projet, la réflexion autour de la visibilité est déjà moins impactante.

Dans les deux cas, une conception approfondie et un merge seront vos meilleures armes.

30.2.3 Ordre des déclarations

Il n'est pas évidenl Ô Ô;t de se rappeler s'il faut écrire

public static int $valeur;
OU
static public int $valeur;
OU
static int public $valeur;

Pour se rappeler que la visibilité est toujours indiquée en premier, il faut se mettre à la place de l'interpréteur : si l'attribut est private, il n'est peut être pas nécessaire de savoir le reste (static/type/valeur).

31. Pourquoi les certifications c'est commercial ?

Qu'on se le dise : ce n'est pas parce que vous avez une certification en informatique dans une technologie que vous êtes un expert dans cette technologie et l'inverse est certainement vrai : ce n'est pas parce que vous vous êtes autoproclamé expert (souvent lié avec le nombre d'années, ou même de mois dans les sociétés de service) que vous arriverez à avoir une certification. Autrement dit, la connaissance et l'expérience peuvent être dans ce contexte deux choses bien différentes.
Une certification n'a a priori qu'un seul intérêt : prétendre auprès de vos clients et/ou employeurs que vous avez une valeur validée par un tier légitimé par la paternité de la technologie ou par divers accords entre la société de formation et l'auteur de la technologie. Sauf qu'un étudiant ou même un jeune salarié qui aura passé une certification ne dupera personne : il n'a pas assez d'expérience professionnelle, il sait juste de manière ultra théorique tout ce qui concerne une technologie, ce qui est en soit très bien mais pas suffisant. A contrario, ce n'est pas parce qu'un professionnel ne connait pas toutes les subtilités d'une technologie qu'il sera inefficace, et ce pour deux raisons :

* la première est qu'internet est un moyen extrémement efficace pour trouver une solution à ses problèmes ; certes, cela peut des fois -dramatiquement- être un copier/coller d'un message venant de stack overflow ou tout simplement provenant de la documentation officielle de la techno, dans ce cas-là, le développeur aura de toute manière dépassé le problème et utilisé la technologie de la bonne manière (ie celle qui a été pensée lors de la conception de celle-ci).
* la seconde est que si cette subtilité est inconnue du professionnel expérimenté, c'est bien parce qu'elle l'est : subtile, il est certes très riche pour l'esprit de connaitre les tréfonds d'une technologie, en attendant, si cela ne sert dans la majorité des cas à rien, c'est une forme de temps perdu. Cependant, non seulement il n'est pas nécessaire de payer un organisme quelconque pour obtenir un A4 qui valide que l'on a bien potassé par coeur, en quelques dizaines d'heures tout au plus, toutes les signatures de fonctions, et à contrario, il est bien plus plaisant de s'instruire sur la technologie sans la volonté de passer un examen, l'employeur/le client n'aura pas de problème à le détecter et il pourrait appeler ça une passion.

Ainsi, un développeur passionné, ce n'est pas un développeur qui passe des certifications pour prétendre que c'est le meilleur (d'autant qu'un bon développeur ne se résume pas à un bon écrivain), c'est un développeur qui s'instruit par Plaisir, et ce plaisir, lorsqu'il arrive à le transmettre (le plaisir ET la connaissance induite, sinon c'est juste du domaine de l'Eros), cela profite à tout le monde.

En bref, rien ne vous empeche de vous former à une certification sans la passer. Non seulement, vous n'aurez pas la pression inutile d'un examen (la plupart du temps sans internet, ce qui est assez ironique), mais en plus vous allez acquérir des connaissances d'une façon extrèmement saine, de plus vous allez surement apprendre des choses qui vous seront surement plus utiles dans le cadre de vos projets.

D'autant que non seulement l'organisme en question va profiter économiquement de cette situation mais en plus cela va le pousser à mettre à jour sa technologie pour aller dans ce sens, et donc d'apporter des nouveautés la plupart du temps complétement superficielle dans sa technologie (ex: du sucre syntaxique, du déplacement de répertoire, un changement de nommage, d'arguments etc...).

On peut se donner des exemples et débattre sur les récentes modifications de SF3, puis SF4, puis SF5 pour s'accorder sur le fait qu'à part faire des mv dans tous les sens,tout était déjà possible sans trop de difficulté avec SF2.8 (la différence entre la 1.4 et la 2.0 justifierait par contre une différence notoire d'appréciation des deux certifications correspondantes).


32. Code écologique

32.1. Qu'est ce que le code le plus écologique ?

C'est un peu paradoxal, mais le code le plus écologique qui soit est souvent celui qui n'existe pas.

Car, s'il n'existe pas, on fait plusieurs économies :
* Economie de développement (cerveaux qui chauffent, IDE qui tourne, partages sur un gestionnaire de version (GIT, SVN etc...))
* Economie de tests automatiques (jenkins, gitlab CI, scrutinizer, tous ces trucs qui s'executent à chaque fois pour controler votre code)
* Economie de déploiement (la compression, l'envoi, le controle de sécurité, la décompression, l'installation (composer install, clear cache etc...))
* Economie d'execution (cron/appel HTTP qui provoque le lancement, l'execution en elle même (I/O, RAM, charge du processeur))
* Economie du SAV (débbogage, mises à jour (volontaire ou non)...)
* Economie de démantèlement (et ce qui s'en suit s'il s'agit d'un remplacement par quelque chose d'autre)

Toutes ses étapes de la vie du code consomment de l'énergie, en très grande partie électrique, et donc en tirant la ficelle tôt ou tard, de l'énergie fossile.

Et donc ? Et bien, avant d'écrire du code, il faudrait se demander s'il est vraiment nécessaire de le faire, et si c'est le cas, d'essayer d'en prévoir l'impact le plus faible possible.

32.2. Un code 'un peu' écologique

Un code peut être considéré comme écologique s'il améliore l'impact environnemental du ou des serveurs qui le ferait tourner, sans délocaliser l'exécution d'autres programmes.
Par exemple :
* du code qui supprimerait des fichiers / programmes / exécutions ;
* du code qui serait plus performant et moins gourmand en ressources.

/!\ ATTENTION : il ne faut pas que le gain apporté par ce code soit inférieur à l'impact des autres étapes nommées plus haut.
Par exemple, si un développeur doit travailler 2 mois pour optimiser un bout de code qui tourne une fois par an et qui consommerait quelques watts ; cela serait contre productif car cela induit des coûts énergétiques supérieurs aux gains.

Il faut donc se poser les bonnes questions :
- combien coûterait l'amélioration du code versus le code déjà en place ?
-> coût du temps de recherche/développement/tests/déploiement/execution
-> coût des ordinateurs qui servent à ces étapes.

32.3. Un code 'vraiment' écologique

Un code peut être considéré comme écologique s'il participe à un projet dont l'objectif est lui-même à portée écologique.
Exemple : calcul de l'optimisation de l'usage d'un terrain agricole, détection et alertes en cas de pollution, détermination de la forme idéal d'un moteur...
En d'autres termes, son impact doit être plus positif que négatif sur l'environnement, ce qui n'est malheureusement quasiment jamais le cas.

Le coût est directement relié à l'empreinte carbone du développement. Il faut également considérer que le salaire du salarié sera également dépensé à nouveau dans des produits ayant eux aussi un impact potentiellement négatif (il pourrait être positif si ce salarié se sent concerné par ses actes personnels vis-à-vis de l'environnement).

32.4. Ironie de cette page

L'impact de la rédaction de cette page a un bilan carbone surement négatif tant que son existence n'a pas éveillé les esprits et eu des conséquences écologiques positives.

33. DateTime




Il est quasiment impossible dans un projet PHP un peu conséquent de ne pas avoir un jour à manipuler des dates.
La difficulté d'apprentissage peut être croissante. Au début, on souhaite uniquement afficher la date du jour au format jj/mm/AAAA ; puis, on cherche à estimer le nombre de jours entre deux dates ; enfin, on cherche à parser des dates.

Et on se rend vite compte que la fonction PHP date() ne suffit pas réellement à faire ce que l'on souhaite.



En général, la classe native \DateTime suffit à résoudre les problèmes les plus communs, je vous invite à consulter cette page-ci où je donne quelques exemples d'utilisation de celle-ci.

33.1. Limites de la classe DateTime

Le problème de la classe native \DateTime est que certains méthodes basiques ont l'air de manquer.

Par exemple, des choses pratiques comme getYear, setDay ou encore des alias du type isoFormat (équivalent au format('Y-m-d')).



Deux solutions s'offrent donc à nous :
* la première est de surcharger la classe DateTime avec de nouvelles méthodes.
* la seconde serait d'utiliser un vendor comme Chronos/Carbon/Moment qui peut répondre à des besoins plus poussés.

33.1.1 Les vendor DateTime

Beaucoup de composants PHP populaires se retrouvent sur la page Github Awesome PHP(https://github.com/ziadoz/awesome-php).
Rien ne permet d'être convaincu que ces vendors sont meilleurs que d'autres, mais leur popularité fait qu'en cas de problème, plus de monde pourra aider.

Voici la liste des vendors orientés manipulation de Date à la mi-2021 :
* CalendR - A calendar management library.
* Carbon - A simple DateTime API extension.
* Chronos - A DateTime API extension supporting both mutable and immutable date/time.
* Moment.php - Moment.js inspired PHP DateTime handler with i18n support.
* Yasumi - An library to help you calculate the dates and names of holidays.

Chronos utilise un trait nommé MagicPropertyTrait qui permet d'avoir des méthodes comme getYear / getMonth / getDay. Il utilise également le trait ModifierTrait pour ajouter des méthodes comme hour/year/month/addYear/addYears...
La méthode toDateString existe également.

Moment.php fournit également des méthodes getYear / getMonth / getDay et également des méthodes comme addDays, subtractDays. On peut également faire un ->format(Moment::NO_TIME).

Ces deux vendors sont au coude à coude concernant la popularité, à la différence que Chronos est maintenu par l'équipe derrière CakePHP, un framework assez connu, on imagine donc que les développeurs ont un peu de bouteille.

Moment est compatible dès PHP5 alors que Chronos nécessite au minimum PHP 7.2.

🧙‍♂️️Chronos l'emporte selon moi.

33.1.2 La classe surchargeant DateTime

💡️Ne pas réinventer la roue.


Il est possible de créer une classe surchargeant la classe \DateTime et de l'utiliser à travers son projet.

Il y a deux avantages certains à faire sa propre classe :
* On ne dépend pas d'un vendor et donc de ces mises à jour ;
* Le projet n'embarque pas du code inutile, on utilise assez rarement toutes les méthodes d'un vendor ;
* Le code correspond parfaitement au besoin, et fait le nécessaire ;

Il y a par contre au moins trois inconvénients :
* On perd du temps à développer et maintenir du code technique (vs code fonctionnel/métier/business) ;
* Si le projet n'est pas open source, on ne profite pas des revues de code de la communauté ;
* Il faut que les développeurs de l'équipe apprennent à utiliser la "couche maison", c'est simple quand il s'agit dun getDay, mais cela peut être plus compliqué avec une méthode qui serait nommée "processBuffstream()" : à moins d'être omniscient, il faut forcément aller voir la mécanique interne ou faire des copiers/collers de ce qui existe déjà, ou encore (dans un monde idéal) : lire la documentation faite par le développeur.

34. Twig Vs Smarty 2

Comparer SmartTPL, Smarty 2 et Twig, c'est comme comparer CVS, SVN et GIT.
On passe pour ainsi dire de l'âge de pierre à l'âge de fer. Nous ne sommes surement pas encore à l'âge d'or.

La grande plus-value du Twig par rapport à Smarty 2 est surement l'héritage entre les templates, évitant non seulement du copier coller entre les différentes pages mais surtout une souplesse bien agréable. On peut éventuellement imaginer que les feuilles de style sont importées dans un fichier dédié et éventuellement overrideraient ce fichier dans une page spéciale du site (ex: une page spéciale Halloween où la page aurait des tons oranges/noirs).

34.1. Twig vs Smarty 3

Le fossé entre Twig et Smarty 3 est moins large qu'entre Twig et Smarty 2. On peut faire globalement la même chose mais la syntaxe de Twig reste plus agréable (même si elle peut rentrer en conflit avec des frameworks JS qui utilisent les doubles moustaches).

Smarty 3 a introduit la fonctionnalité d'héritage, rattrapant son retard. Mais il ne font guère mieux que l'original en copiant jusqu'à la syntaxe :

    {block name=title}My Page Title{/block}

Prestashop est actuellement le projet le plus ambitieux utilisant Smarty, à voir dans les années qui viennent s'ils décident ou non de migrer chez la concurrence, ce qui n'est pas à exclure s'ils cherchent un jour à utiliser plus que quelques composants de Symfony 3.4.

35. PhpStorm

35.1. Empecher l'insertion automatique des guillemets simples ou doubles


Editor > Smart Keys
Décocher "Insert pair quote"

📖️️https://www.jetbrains.com/help/phpstorm/completing-punctuation.html


36. Résumé de "Clean Code" par Robert C. Martin

📖️️Traduction de https://gist.github.com/cedrickchee/55ecfbaac643bf0c24da6874bf4feb08(https://gist.github.com/cedrickchee/55ecfbaac643bf0c24da6874bf4feb08)


Un résumé des principales idées du livre "Clean Code : A Handbook of Agile Software Craftsmanship" de Robert C. Martin (alias Oncle Bob).

Le code est propre s'il peut être compris facilement - par tous les membres de l'équipe. Un code propre peut être lu et amélioré par un développeur autre que son auteur original. Avec la compréhensibilité viennent la lisibilité, la modifiabilité, l'extensibilité et la maintenabilité.

36.1. Règles générales

36.1.1 Suivez les conventions

* Restez simple et stupide. Le plus simple est toujours le mieux. Réduisez la complexité autant que possible.
* Règle des scouts. Laissez le terrain de camping plus propre que vous ne l'avez trouvé.
* Toujours trouver la cause profonde. Cherchez toujours la cause profonde d'un problème.
* Suivez le principe de la moindre surprise.
* Ne vous répétez pas (DRY).
* Ne pas passer outre les sécurités.

36.2. Règles de conception

* Gardez les données configurables (ex. : constantes) à un niveau élevé. Elles doivent être faciles à modifier.
* Préférez le polymorphisme à if/else ou switch/case.
* Séparer le code multithreading.
* Empêcher la sur-configurabilité.
* Utiliser l'injection de dépendances.
* Suivre la loi de Déméter. Une classe ne doit connaître que ses dépendances directes.

36.3. Conseils de compréhensibilité

* Soyez cohérent (consistent). Si vous faites quelque chose d'une certaine manière, faites toutes les choses similaires de la même manière.
* Utilisez des noms de variables explicites.
* Encapsulez les conditions limites. Il est difficile de garder une trace des conditions limites. Mettez le traitement de ces conditions à un seul endroit.
* Préférez les objets aux types primitifs.
* Évitez les dépendances logiques. N'écrivez pas de méthodes qui fonctionnent correctement en fonction de quelque chose d'autre dans la même classe.
* Évitez les conditions négatives.

36.4. Règles relatives aux noms

* Choisissez des noms descriptifs et non ambigus.
* Faites des distinctions significatives.
* Utilisez des noms prononçables.
* Utilisez des noms faciles à chercher (évitez les noms très courts, très récurrents).
* Remplacer les nombres magiques par des constantes nommées.
* Évitez les codages. Ne pas ajouter de préfixes ou d'informations de type.

36.5. Règles de fonctions

* Petites.
* Elles ne font qu'une chose et doivent la faire bien.
* Utilisez des noms descriptifs.
* Préférez moins d'arguments. Pas plus de 2 arguments si possible.
* N'ont pas d'effets de bord (ie. se limite à ce qu'elles sont censées faire).
* Ne pas utiliser d'arguments de type "flag(https://martinfowler.com/bliki/FlagArgument.html)". Diviser la méthode en plusieurs méthodes indépendantes qui peuvent être appelées depuis le client sans ce type d'argument.

36.6. Règles de commentaires

Essayez toujours de vous expliquer dans le code. Si ce n'est pas possible, prenez votre temps pour écrire un bon commentaire.
* Ne soyez pas redondant (par exemple : i++ ; // incrémente i).
* N'ajoutez pas de commentaires évidents. (par exemple: ->save() // sauvegarde la donnée).
* N'utilisez pas d'accolades fermantes (par exemple : } // fin de fonction).
* Ne commentez pas le code mort. Supprimez-le plutôt.
* Utiliser les commentaires pour expliquer l'intention.
* Utiliser les commentaires pour clarifier le code.
* Utiliser les commentaires pour avertir des conséquences.

36.7. Structure du code source

* "Séparez les concepts verticalement."
* "Le code connexe doit apparaître verticalement dense."
* Déclarez les variables à proximité de leur utilisation.
* Les fonctions dépendantes doivent être proches.
* Les fonctions similaires doivent être proches.
* Placez les fonctions dans le sens de la descente.
* Gardez les lignes courtes.
* N'utilisez pas l'alignement horizontal.
* Utilisez les espaces blancs pour associer des éléments connexes et dissocier des éléments faiblement connexes.
* Ne rompez pas l'indentation.

36.8. Objets et structures de données

* Cachez la structure interne.
* Préférez les structures de données.
* Évitez les structures hybrides (moitié objet et moitié données).
* Doivent être petites.
* Ne font qu'une seule chose.
* Un petit nombre de variables d'instance. Si votre classe a trop de variables d'instance, alors elle fait probablement plus d'une chose.
* La classe de base ne doit rien savoir de ses dérivés.
* Mieux vaut avoir plusieurs fonctions que de passer du code dans une fonction pour sélectionner un comportement.
* Préférez les méthodes non statiques aux méthodes statiques.

36.9. Tests

* Une assertion par test.
* Rapide.
* Indépendant.
* Répétable.
* Auto-validation.
* Rapide.
* Lisible.
* Facile à exécuter.
* Utilisez un outil de couverture.

36.10. Senteurs du code

* Rigidité. Le logiciel est difficile à modifier. Une petite modification entraîne une cascade de modifications ultérieures.
* Fragilité. Le logiciel se casse en de nombreux endroits à cause d'une seule modification.
* Immobilité. Vous ne pouvez pas réutiliser des parties du code dans d'autres projets en raison des risques encourus et de l'effort élevé.
* Complexité inutile.
* Répétition inutile.
* Opacité. Le code est difficile à comprendre.

36.11. Gestion des erreurs

Ne mélangez pas la gestion des erreurs et le code.
Utilisez des exceptions au lieu de renvoyer des codes d'erreur.
Ne retournez pas null, ne passez pas null non plus.
Lancez les exceptions avec le contexte.

37. Code Smell



📖️️Lire ceci directement : https://fr.wikipedia.org/wiki/Code_smell

38. Les codes à éviter

Lorsqu'on débute en PHP, on commence par écrire du code en suivant les mauvaises pratiques.
La plupart du temps, ce code est un héritage de celui qu'on pouvait écrire en 2005. A cette époque, le PHP balbutiait encore. En 2020, ce code ne devrait plus exister, car il est toujours possible de suivre les bonnes pratiques même avec du PHP procédural.

38.1. Utiliser le or die

On rencontre l'utilisation du 'or die' lorsque le développeur copie/colle un exemple d'utilisation du mysql_connect. On retrouve alors ce genre de ligne :

$bd = mysql_connect($nomserveur, $login, $pass) or die("Connexion échouée");

38.1.1 Pourquoi il ne faut pas utiliser le or die

Il n'est pas conseillé de forcer l'arrêt de l'interpréteur PHP.
Dans le cas d'une communication entre un serveur et un client, le client, lorsqu'il fait une requête HTTP s'attend à avoir une réponse du serveur. Ici, il reçoit la chaine de caractère "Connexion échouée". Non seulement le client ne devrait pas connaitre ce genre d'information, mais en plus il ne peut pas savoir ce qu'il doit en faire.

38.1.2 Ce qu'il faut faire

Il faut :
1. arrêter d'utiliser le die (sauf éventuellement pour débugguer) ;
2. utiliser le retour du mysql_connect pour gérer l'erreur de façon plus censée :
2.1 mettre à jour le journal d'évenement (log) en indiquant l'erreur (date/ip/contexte) ;
2.2 retourner une erreur au client dans un format structuré (ex: json) associée à un code d'erreur HTTP, ici cela peut être le code 500 puisque l'erreur vient manifestement du serveur WEB.

38.2. Utiliser un die/exit dans une fonction

Ce type de code introduit un danger :

function connect_bd(nomserveur, $login, $pass)
{
    $db = mysql_connect($nomserveur, $login, $pass);
    if (!$db) {
        die("Connexion échouée");
    }

    return $db;
}

38.2.1 Pourquoi ?

Utiliser un exit ou un die dans une fonction rend complexe la gestion des erreurs.

38.2.2 Alternative

On peut lancer une exception au niveau de la fonction afin de récupérer plus proprement l'erreur.
Voici un exemple :

function connect_bd(nomserveur, $login, $pass)
{
    $db = mysql_connect($nomserveur, $login, $pass);
    if (!$db) {
        throw new \Exception("Connexion échouée");
    }

    return $db;
}

Note : \Exception peut ici être remplacée par une classe plus spécifique (ex: "MySQLException").

38.3. Utiliser le tag ?>

Lorsque le tag PHP est fermé, tout ce qui le suit est tout de même retourné par le serveur WEB.
C'est-à-dire que des caractères blancs supplémentaires peuvent être retournés et interprétés par le navigateur comme du HTML.
Or, le HTML, c'est avant tout une syntaxe à base de balises à respecter, et quand on utilise le ?>, on ne contrôle plus ce qui est retourné par le serveur WEB. Dans la grande majorité des cas, cela est sans conséquence ; le résultat est interprété comme du HTML invalide et la page s'affiche tout de même. Dans la minorité des cas, cela a de graves conséquences ; cela peut entrainer des problèmes d'affichage (dans le cas où un <pre/> a été ouvert par exemple) ou même des erreurs JS du côté du client (le JS pense recevoir un objet JSON et il reçoit des caractères supplémentaires).

38.3.1 Ce qu'il faut faire

Il ne faut pas utiliser le tag ?>. Le serveur doit interpréter les espaces et les retours à la ligne comme du code PHP.
Il faut respecter les deux règles suivantes :
1. Ne jamais mélanger du HTML et du PHP dans le même fichier ;
2. N'utiliser qu'une et une seule fois le tag <?php par fichier .php ;

38.4. Ne pas espacer les symboles +,*,-,/,=

C'est surtout une question de confort de lecture et de norme de codage.

38.5. Mélanger du HTML et du PHP dans le même fichier

Le net regorge d'exemples où dans un fichier .PHP, on ouvre et on ferme des balises PHP pour écrire du HTML.
Si l'on souhaite construire plusieurs modèles de page selon le site web, autant isoler dès le départ les morceaux de templates dans des fichiers séparés.

38.6. Utiliser le double égal '=='

Alors, le double égal ne prend pas en compte le typage fort et ça c'est un problème.
Car toutes ces choses là retourne TRUE :

<?php
0 == false;
1 == 1.0;
true == 'chataigne';
!null == true;

Ceci peut génèrer des anomalies. En pratique, on compare une donnée qui vient de la base de données, d'un système de cache ou d'un formulaire avec une valeur en dur dans le code, et c'est souvent un drame car le code peut avoir un comportement non prévu par la suite si le test retourne soit disant TRUE.

38.6.1 Ce qu'il faut faire

Utiliser le triple égal '==='.

38.7. Utiliser un God Object

Copie de la page Wikipedia : God object(https://fr.wikipedia.org/wiki/God_object)

Un God object est, dans le domaine de la programmation orientée objet, un objet qui reconnaît trop de choses ou fait trop de choses. Le god object est un exemple d'antipattern (ou anti-patron).

Le principe général de la programmation structurée est de s'attaquer à un problème important en le divisant en plus petits problèmes à résoudre (stratégie de diviser pour régner). Une fois chacun des petits problèmes résolus, le problème général est automatiquement réglé. Ainsi, il n'y a qu'un objet auquel il doit être connu ou renseigné : lui-même. De la même façon, il n'y a qu'un seul ensemble de problèmes auquel un objet doit se confronter : l'ensemble des siens propres.

La programmation « god object » ne suit pas cette approche. Au lieu de cela, la plus grande partie du programme consiste en un seul bloc qui est renseigné sur tout et maintient constamment à jour données ou informations sur le programme, et fournit la plupart des fonctions et des algorithmes qui utilisent ces données. Du fait que cet objet supporte et organise tellement d'informations à lui seul, il joue un rôle identique à celui d'un dieu. Au lieu de blocs de programme communicant indépendamment entre eux et sans intermédiaire, les autres parties du programme sont dépendants du god object pour communiquer et prendre leurs informations. Comme le god object est référencé par tout le reste de la programmation, la maintenance de celle-ci devient très difficile, y compris dans les plus ordonnés des programmes.

Un god object est la version « orientée-objet » de l'incapacité à concevoir correctement les sous-programmes dans un langage de programmation procédural, ou d'utiliser trop de variables globales pour y stocker des informations sur l'état du programme à un moment donné (comme les drapeaux).

Bien que la création d'un god object soit considérée comme une mauvaise pratique de programmation, cette technique est à l'occasion utilisée dans les environnements critiques de programmation (comme les microcontrôleurs), où le gain dans la vitesse d'exécution et la centralisation du contrôle sont des facteurs plus importants que la facilité de maintenance et l'élégance de la programmation.

38.8. Dupliquer du code

Copie de la page Wikipedia : Duplication de code(https://fr.wikipedia.org/wiki/Duplication_de_code)

La duplication de code en programmation informatique est une erreur courante de conception de logiciels où une suite d'instructions similaires (voire identiques) existe en plusieurs endroits du code source d'un logiciel.

La duplication de code arrive à la suite de la programmation par copier-coller. C'est une erreur classique de débutants en programmation informatique ; cependant, cette erreur touche également les développeurs confirmés.

Le code dupliqué pose des problèmes de maintenance dont l'importance augmente avec la quantité de code dupliqué. Plus le code est dupliqué, plus il y a de code à maintenir. Par exemple :

si le code copié a une erreur de programmation, cette erreur se répète à chaque copier-coller, et corriger cette erreur nécessite de modifier tous les endroits où le code est dupliqué ;
si le code dupliqué doit évoluer, il faut alors modifier tous les endroits où le code est dupliqué pour y parvenir ;
un code dupliqué peut masquer des différences minimes, mais essentielles, qui existent avec une autre portion de code similaire.
L'antipattern correspondant est l'erreur de copier/coller : Il s'agit d'un copier-coller de code, où le code collé n'a pas été adapté à la portion de code environnante ce qui entraîne des incohérences. Cela arrive généralement à cause d'un défaut de vérification de la part du programmeur. La meilleure solution étant de factoriser les parties communes au lieu de les dupliquer.

La technique permettant d'éviter la duplication de code est la factorisation du code et celle permettant de s'en débarrasser est le réusinage du code.
Test Driven Development est un processus de développement permettant d'éviter le code dupliqué tout en disposant d'un code bien testé.
DRY (Don't repeat yourself! en anglais, ou Ne vous répétez pas) est un principe de programmation encourageant à éviter la duplication de code (dans laquelle le programmeur se répète).
Des logiciels tels PMD, permettent d'identifier le code dupliqué dans une base de code.

38.8.1 Utiliser la règle de trois

Copie de la page Wikipedia : Règle de trois(https://fr.wikipedia.org/wiki/R%C3%A8gle_de_trois_(programmation_informatique))

La règle de trois est une règle empirique de refactorisation de code pour décider quand des morceaux de code similaires doivent être refactorisés pour éviter la duplication de code. Cette règle indique que deux instances de code similaire ne nécessitent pas de refactorisation, mais lorsqu'un code similaire est utilisé trois fois, il doit être extrait dans une nouvelle procédure. La règle a été popularisée par Martin Fowler dans Refactoring 1 et attribuée à Don Roberts.

La duplication est considérée comme une mauvaise pratique en programmation car elle rend le code plus difficile à maintenir. Lorsque la règle codée dans un morceau de code répliqué change, celui qui gère le code devra le changer correctement à tous les endroits.

Cependant, le choix d'une conception appropriée pour éviter la duplication pourrait bénéficier de plus d'exemples pour voir les modèles. Tenter une refactorisation prématurée risque de sélectionner une mauvaise abstraction, ce qui peut entraîner un code pire lorsque de nouvelles exigences émergent 2 et devront éventuellement être à nouveau refactorisées.

La règle implique que le coût de la maintenance l'emporte certainement sur le coût de la refactorisation et une mauvaise conception potentielle lorsqu'il y a trois copies, et peut-être ou non s'il n'y a que deux copies.

39. Les conventions de nommage

C'est le genre de sujet qui fait couler beaucoup de sang entre pas mal de développeurs, c'est un peu dommage car ce serait plus intéressant qu'ils débattent plutôt à propos des RFC, histoire que la controverse s'élève au dessus des préférences personnelles.

Le PHP est surement encore décrié car le langage lui même n'a pas respecté à ses débuts de convention de nommage, notamment au niveau de ses fonctions.
On retrouve ainsi des noms de fonctions comme strtolower (flatcase), str_pos (snake_case) et d'autres curiosités comme bin2hex (le 2 voulant dire to) et cal_to_jd
Concernant le nommage des classes (PascalCase) et de leurs méthodes (camelCase), rien à signaler.

39.1. camelCase

Il est souvent confondu avec le PascalCase alors que la première lettre du premier mot doit être en majuscule.
Le camelCase est utilisé en PHP pour le nom des méthodes de classe.
Tous les mots sont attachés, la première lettre de chaque mot dès le deuxième mot est en majuscule.

Exemple: doSomething

39.2. PascalCase

Le PascalCase ou StudlyCaps est actuellement la convention de nommage à utiliser pour le nom des classes en PHP.

Tous les mots sont attachés, la première lettre de chaque mot est en majuscule.

Exemple : ChickenIterator : nom de classe composé des deux mots chicken et iterator.

39.3. snake_case

Le snake_case est utilisé dans certains noms de fonctions natives de PHP.

Tous les mots sont séparés par un underscore, les lettres de chaque mot sont en minuscule.

Exemple : chicken_iterator

39.4. kebab-case

Le kebab-case n'est pas utilisé en PHP, mais est utilisé en HTML pour le nommage des classes ou dans le nom des URLs.

Tous les mots sont séparés par un tiret, les lettres de chaque mot sont en minuscule.

Exemple : chicken-iterator

39.5. flatcase

Tous les mots sont attachés, les lettres qui les composent sont en minuscule.

Exemple: strtolower
# UPPER_CASE
L'UPPER_CASE est utilisé en PHP pour les noms des constantes.
Les mots sont séparés par des underscore, les lettres qui les composent sont en majuscule.
Pendant un moment, c'était aussi conventionnel de nommer les colonnes de ses bases de données en UPPER_CASE. Cela dérive actuellement plutôt vers du snake_case.

Exemple : STR_PAD_RIGHT

39.6. WikiCase

Rarement entendu autour d'une table, c'est du PascalCase où il est imposé que chaque lettre majuscule ne soit jamais suivie par une autre lettre majuscule.

Ex: CreateAJob est interdit et doit être remplacé par CreateJob

40. Les normes

Voici les normes que le projet doit suivre, exemples à l'appui.
On adaptera bien sûr les règles aux contraintes de la version de PHP utilisée sur les serveurs de production.

40.1. Exemple d'une classe

<?php

namespace App\Feature;

use App\Service;

class MyClass implements MyInterface
{
    public function myMethod(): bool
    {
        if () {

        }

        while() {

        }

        do {

        } while ();

        return true;
    }
}

Plusieurs règles sont à respecter :
* l'emplacement des accolades
** à la ligne pour la déclaration de la classe et de ses méthodes
** même ligne pour les conditions
* l'emplacement des espaces :
** entre le if/do/while/for/foreach/switch et les parenthèses
* l'emplacement des retours à la ligne :
** retour à la ligne avant le return/if/while/do/switch

* le nom des classes : en StudlyCase (PSR-1)
* le nom des méthodes : en camelCase (PSR-1)
* la visibilité la moins permissive possible est toujours indiquée, que ce soit pour les méthodes/constantes/attributs
* le type d'attribut/argument est toujours indiqué, le type mixed est interdit
* le type de retour des méthodes est toujours indiqué, même le void, le type mixed est interdit
* utiliser le mot clef static partout où cela est possible
* toute classe doit implémenter une interface directement ou par héritage
* toujours utiliser le cas default dans un switch/match

40.2. Plus de code procédural

Mis à part celui de l'autoload, le projet doit être codé en full POO :
* Plus de fonctions seules : on n'utilise que des méthodes de classes.
* Ne pas utiliser de variable globales mise à part les superglobales

40.3. Utilisation des superglobales

Les superglobales ($GLOBALS, $_SERVER, $_GET, $_POST, $_FILES, $_COOKIE, $_SESSION, $_REQUEST, $_ENV) ne doivent être utilisées qu'au plus une et une seule fois dans le projet.
Il est strictement interdit d'en modifier directement la valeur.

40.4. Templating

Ne pas écrire de HTML dans un fichier PHP, utiliser un moteur de template ou créer sa propre solution.
Ne pas utiliser de fonctions natives de sortie standard plus d'une fois dans l'ensemble du projet.

40.5. Fonctions natives PHP

Autant que faire ce peut, ne pas utiliser plus d'une fois les fonctions natives de PHP dont le rôle est très précis (ex: htmlentities, json_decode...) ou dont le nombre d'arguments facultatifs est important.
Les encapsuler si besoin en définissant des classes à responsabilité unique (ex: class Templater, méthode escape($htmlContent)).

Bien sûr, le but n'est pas de faire une classe par fonction PHP...

41. Acronymes de bonnes pratiques

41.1. SOLID

* Single responsibility principle (Responsabilité unique)
* Open/closed principle (Ouvert/fermé )
* Liskov substitution principle (Substitution de Liskov )
* Interface segregation principle (Ségrégation des interfaces)
* Dependency inversion principle (Inversion des dépendances)

41.2. KISS

Keep It Simple, Stupid (garde ça simple, idiot)

🧙‍♂️️Cette phrase peut être détournée et utilisée pour des motifs non valables.
Le KISS concerne de mon point de vue uniquement la facilité d'utilisation du logiciel, et non sa conception technique.


41.3. DRY

Don't Repeat Yourself (Ne vous répétez pas)

📖️️Wikipédia : https://fr.wikipedia.org/wiki/Ne_vous_répétez_pas(https://fr.wikipedia.org/wiki/Ne_vous_r%C3%A9p%C3%A9tez_pas)


41.4. MoSCoW

* Must have this (doit être fait)
* Should have this if at all possible (devrait être fait dans la mesure du possible)
* Could have this if it does not affect anything else (pourrait être fait dans la mesure où cela n'a pas d'impact sur les autres tâches)
* Won't have this time but would like in the future (ne sera pas fait cette fois mais sera fait plus tard)

41.5. YAGNI

You Ain't Gonna Need It (vous n'en aurez pas besoin)

📖️️Wikipédia : https://fr.wikipedia.org/wiki/YAGNI(https://fr.wikipedia.org/wiki/YAGNI)


41.6. GRASP

General Responsibility Assignment Software Patterns (lignes directrices pour l'attribution de la responsabilité des classes et des objets en conception orientée objet)


# Loi de Demeter
La Loi de Déméter pour les fonctions requiert que toute méthode M d'un objet O peut simplement invoquer les méthodes des types suivants d'objets :

* O lui-même
* les paramètres de M
* les objets que M crée/instancie
* les objets membres de O

📖️️Wikipédia : https://fr.wikipedia.org/wiki/Loi_de_Déméter(https://fr.wikipedia.org/wiki/Loi_de_D%C3%A9m%C3%A9ter)


# Loi de Leblanc
Later Equals Never

Utilisé en particulier pour cibler les @TODO qui font partie du code pendant des années. Il est en effet assez rare que le @TODO soit traité par un autre développeur que celui d'origine.


42. PSR-1: Basic Coding Standard

42.1. Introduction

Cette toute première PSR toujours valide, la bien nommée PSR-1(https://www.php-fig.org/psr/psr-1/) pose le décor : il est vivement recommandé lorsqu'on fait du PHP de faire du POO, cela ne semble par contre pas obligatoire (voir la conclusion).

42.2. Résumé des règles

* Les fichiers PHP doivent uniquement utiliser la balise <?php
* Les fichiers doivent uniquement être encodés en UTF-8 sans entête BOM
* Les fichiers doivent soit être déclaratifs (constances, classes, fonctions), soit être à effet de bord (modifie des fichiers, appels des services), jamais les deux en même temps
* Les namespaces et classes doivent respecter la convention de nommage de la PSR-4
* Les noms des classes sont en StudlyCaps
* Les noms des constantes de classes sont toujours en majuscule et espacé si besoin par des underscores.
* Les noms des méthodes doivent être déclarés en camelCase.

42.3. Les règles une par une

42.3.1 Les balises PHP

42.3.1.1 Une et une seule balise

Bien qu'il existe d'autres balises, une seule balise doit être utilisée, la balise <?php
La balise ?> ne doit pas être utilisée:
* Le code HTML est de toute manière toujours séparé du code PHP
* cela évite de laisser des caractères en fin de fichier interprété comme des caractères HTML

42.3.1.2 Exemple d'utilisation

Tous les fichiers .php de votre projet doivent commencer par cette ligne suivie d'un retour à la ligne :

<?php

42.3.2 UTF-8 sans BOM

D'où la nécessite d'écrire du code avec des outils dédiés (exit donc le wordpad de Windows par exemple).
Le BOM est une entête de fichier pratiquement invisible lorsque l'on code. Au moment de l'appel au serveur, ce caractère est le premier que le navigateur voit lorsqu'il pense récupérer le contenu de la page (avant donc le <!DOCTYPE ou <html>).
Comme ce caractère est en dehors de toute balise meta qui indiquerait l'encodage de la page, le navigateur va faire comme il souhaite puisqu'il considère que la page html n'est pas valide, tous les caractères sont alors affichés en non UTF-8.

42.3.2.1 Exemple d'IDE

Voici plusieurs outils qui ont la notion d'encodage et de BOM:
Sûrs :
* PHPStorm 2020.3
* Geany 1.36

Probables :
* NodePad++
* Visual Studio
* SublimeText
* Atom
* Eclipse

Peu probables :
* Wordpad
* Gedit
* Kate
* Mousepad
* Leafpad
* Pluma
* KWrite et Kedit
* Nedit/TEA ?

Certains outils de traitement de texte en ligne de commande n'injectent pas d'entête BOM :
* emacs (et XEdit)
* vim (et Gvim)
* nano

42.3.3 Fichiers purement déclaratifs ou à effet de bord (side-effect)

42.3.3.1 Fichier PHP avec effet de bords == PHP procédural

Si un fichier PHP, lors de son execution provoque ceci,on considère qu'il a des effets de bord :
* génèrer une sortie (avec echo, print_r et autres)
* lire, créer ou modifier un fichier (file_put_contents, fwrite et autres)
* modifier la configuration de PHP (set_ini)
* utiliser un require/include, interdit de toute manière en dehors des choses comme l'autoloader
* faire appel à un service externe (HTTP, FTP, n'importe quoi)
* génèrer des erreurs
* modifier des variables globales ou statiques

Ce qui est globalement très souvent le cas lorsque l'on fait du procédural, à moins de faire du code qui ne fait rien.

42.3.3.2 Fichier PHP sans effet de bords == PHP POO

En bref, un fichier déclaratif ne doit rien faire sauf déclarer, c'est à dire :
* tous les fichiers de classes/interfaces sont déclaratifs
* tous les fichiers PHP qui déclarent uniquement des fonctions (utilisation du mot clef function), des constantes (utilisation du mot clef define)

Cependant, il y a certaines ambiguités, aujourd'hui nous avons tendance à créer des classes de type Controller ou même Command.
Ces deux là remplacent dans le concept les fichiers de type command.php et index.php que l'on manipulait en non POO.

En d'autres termes, la PSR-1 est clairement du côté de l'usage de la POO. Les fichiers PHP de type procédurale sont à oublier dans le code source de votre application.

42.3.4 Une execution dépend forcément d'un fichier écrit en PHP procédural

Il ne faut pas penser qu'une application en PHP ne peut tourner qu'avec du POO vu que ce n'est que du déclaratif, car il y a forcément au moins dans votre projet un fichier quelque part écrit en procédural pour executer tout ce beau monde.
Ex: via composer 1.10, le fichier autoload.php est celui qui se charge d'executer tout ce petit monde.

Donc, pour faire simple et respecter cette règle :
* tous les fichiers PHP de votre projet sont en POO use/class/interface/namespace et sont donc déclaratif
* le seul et unique fichier de type side-effect est l'autoload, bien souvent géré par Composer ou par un autre Framework

/!\ ce qui est appelé effet de bord est le fait que la ligne s'execute, pas qu'elle soit utilisée dans une définition.
Par exemple, on peut très bien définir une méthode dans une classe qui ferait un appel à un service externe.
Mais le fait de charger cette classe ne doit effectivement pas executer la ligne en question, sauf si c'est demandé par un fichier executif.

42.3.4.1 Un fichier PHP == Une seule classe ou interface

Bien que dans sa cuisine interne d'optimisation, le framework que vous utilisez fait une fusion de toutes les déclarations. Lors du développement, il faut créer autant de fichier que de classe/interface.

42.3.5 L'autoloading

Fini le temps où les fichiers PHP sont importés dans son code via des require ou des include. Chaque fichier PHP doit déclarer en entête son namespace (cf la PSR-4 pour le nommage) et importer les autres fichiers via des use.
Il y a des exceptions : l'autoloader est le seul à pouvoir faire des includes, idem pour un système personnalisé de mise en cache du code PHP.

42.3.6 Nommage des classes

Le nom des classes et des fichiers de classe doivent être en StudlyCaps, c'est à dire que tous les mots sont reliés ensemble sans espace et commencent chacun par une majuscule.
Ex: SecurityController pour le nom de la classe et SecurityController.php pour le nom du fichier.

42.3.7 Nommage des constantes

Toutes les constantes doivent être définies dans des classes ou des interfaces.
En static car il n'est pas être nécessaire d'instancier la classe.

Pendant un temps la visibilité unique des constantes était public, on peut désormais changer cette visibilité.

42.3.7.1 Exemple de déclaration de constante

Voilà comment on peut définir des constantes :

<?php
namespace App;

class MyClass
{
    public static const MY_STRING_CONST = 'valeur';
    protected static const MY_BOOL_CONST = true;
    private static const MY_INT_CONST = 8;
    public static const MY_FLOAT_CONST = 8.08;
}

42.3.7.2 Exemple d'utilisation d'une constante dans un namespace commun

Voilà comment on peut appeler une constante public (ne pas oublier le use en entête) :

<?php
namespace App;

echo MyClass::MY_STRING_CONST;

42.3.7.3 Exemple d'utilisation d'une constante dans un namespace différent

Si la classe est dans un autre namespace, par exemple elle est déclarée dans la classe App\MyClass et est appelée en procédural dans le namespace Walt, il faut l'indiquer dans un use.
<?php
namespace Walt;

use App\MyClass;

echo MyClass::MY_STRING_CONST;

42.4. Conclusion : POO obligatoire ou non ?

Alors, est il possible de faire du PHP sans faire du POO en respectant la PSR-1 ? Je pense que la réponse est Oui.

Imaginons deux fichiers :
* index.php : uniquement du procédural (pas de définition), fait un require_once('lib.php');
* lib.php : uniquement des définitions de fonctions.

index.php :

<?php
require_once('lib.php');
myFunction();


lib.php :

<?php
function myFunction()
{
    // Peu importe, du code.
}


A priori, ce n'est pas de la POO mais cela respecte les différentes règles de la PSR-1.

On peut même aller plus loin en créant autant de librairies que l'on souhaite avec des noms plus judicieux et les injecter avec d'autres require.

La PSR-1 ne rend donc pas obligatoire la POO mais l'incite fortement vu que l'accent est fortement mis sur l'application de la PSR-4.

43. PSR-4: Autoloader


43.1. Pourquoi la PSR-0 a été remplacée par la PSR-4 ?

La réponse est toute simple, la PSR-0 introduisait la notion de namespace en PHP mais cela ne concernait que les vendors du projet et non pas le projet lui même.
Ce qui est en soi assez dommage de ne pas pouvoir utiliser l'élégance des namespaces au sein du code de son application.

44. Que faire des vieilles applications ?

44.1. Le problème

44.1.1 Que veut dire le terme Legacy ?

Legacy est un mot anglais qui veut uniquement dire héritage. En informatique, on appelle en général Legacy le code source original du projet, bien souvent avant l'implémentation d'un framework.
Mais cela pourrait très bien faire référence au code de la veille, il faut que celui-ci soit considéré comme obsolète et qu'il soit toujours executé en production.

44.1.2 Traits typiques d'une application Legacy

Alors, la vieille application que tout le monde connait, c'est celle-ci :
* du PHP procédural ;
* une page web générée par des echo ou des ouvertures et fermetures de tags php ;
* des ouvertures / fermetures d'accès à la base de données dans chaque fichier ;
* des variables globales qui portent des noms évocateurs comme mysql_password default_admin_name ;
* du copier/coller de fonctions mélangeant des noms de variables comme $a, $b, $tartiflette... ;
* des appels par référence utilisés sans justification ;
* de la mise en cache gérée un peu partout pour tout et n'importe quoi ;
* des "scripts cron" qui réparent les erreurs des "scripts web" ;
* des librairies en version 0.4.0-modifie-par-luc-le-dev-parti-depuis-3-ans compatible avec une version particulière de PHP ;
* du franglais car les développeurs ont trouvé un compromis : chacun fait comme il veut ;
* les tests sont faits manuellement avec le navigateur web.

44.1.3 Les problèmes qu'engendre une application Legacy

Tout le monde peut les lister :
* fuite des développeurs ;
* charge de maintenance technique (des bugs, des rattrapages) ;
* clients mécontents, ambiance morose au travail ;
* évolutions fonctionnelles très difficiles à implémenter ;
* fonctions métiers floues.

44.1.4 Est-ce si grave ?

La vrai question à se poser est celle-ci :
Combien cette application rapporte à l'entreprise VS combien cette application coûte à l'entreprise ?
Même si l'entreprise dépense énormement d'argent pour maintenir tout ce beau monde en vie, est-ce qu'elle gagne tout de même de l'argent ? La réponse est souvent affirmative :tant que les clients sont là et que l'application répond dans les grandes lignes à leurs demandes, cela suffit pour vendre.

44.2. Comportement 1 : tout refaire et tout de suite

44.2.1 Pourquoi la refonte totale n'est pas forcément judicieuse

Avant d'envisager de tout casser pour reconstruire, il faut d'abord répondre à toutes ces questions :
* Combien ça va prendre de temps de faire un code iso-fonctionnalité et donc combien d'argent l'entreprise doit dépenser pour ça ?
* Y a-t-il vraiment un gain financier derrière (on fait rarement une refonte au sein d'une société pour la gloire) ?
** Cela peut-il réduire le temps de chargement des pages ?
** Cela peut+il augmenter le nombre de clients et donc de ventes ?
** Cela va-t-il réduire la charge en maintenance technique et du SAV auprès du client ?
** Cela va-t-il réduire la charge du SAV auprès du client ?

Enfin pour résumer :
* Est-ce que l'entreprise va gagner plus d'argent après cette refonte ? Combien ? Quand ?

Si vous ne pouvez pas répondre à cette dernière question, chiffres à l'appui, est-ce que vous pensez réellement que l'entreprise va prendre aveuglément le risque de dépenser l'argent que lui rapporte la vente de ses produits via l'application actuelle ?

44.2.2 Avec ou sans refonte, il faut toujours penser à l'après

Dans un monde utopique et merveilleux où vous refaites tout gratuitement, avez-vous penser à la suite ?
* qui gérera les futurs bugs et combien cela coutera ?
* qui modifiera le futur code et a-t-on la garantie qu'il ne va pas réduire la qualité de la refonte ?
* qui mettra à jour la version du framework/des librairies/du langage ?
* est ce que l'application sera toujours utilisée ?

44.2.3 Remplaçons le framework maison par un framework populaire : la petite histoire

Voici un exemple concret : en 2011, une entreprise spécialiste dans la vente en ligne de DVD a fait le choix d'utiliser symfony 1.4 pour refaire son application mère.

Tout porte à croire que le framework va faire des miracles, après tout il est dans le top 5 des frameworks français.

Bilan plusieurs années plus tard :
* le framework a mal été mis en place
** les développeurs n'avaient pas été formés
** certaines pratiques controversées de code ont été maintenues
* la maintenance de l'application est terrible
** la documentation sur cette version du framework n'existe quasiment plus
** la communauté s'est tournée vers des solutions plus récentes
** aucun développeur du marché ne veut travailler sur le code source
* l'entreprise pleure :
** elle a perdu de l'argent (temps de conception/développement/test...)
** un à plusieurs développeurs sont partis, aussi bien ceux à l'ère pré-framework que ceux à l'ère post-framework

Et à ceux qui dise "il fallait le mettre à jour en version 2.0 au bon moment", mettez-vous une seconde à la place de l'entreprise : "je ne comprends pas, je viens de payer ce projet qui a pris 1 an plus de 100 000€ et maintenant tu me dis qu'il faut encore 6 mois pour le mettre à jour ? Et demain ? ".

Quelle est la véritable erreur ? Concevoir son application en mélangeant le code métier ET le code technique.

Cette petite histoire nous apprends deux choses :
* il peut être dangereux d'utiliser un framework sans avoir à l'esprit que le code du projet devrait pouvoir être indépendant de celui-ci
* un projet legacy 2020 n'est pas forcément la même chose qu'un projet legacy 2010

44.2.4 On ne peut pas tout changer du jour au lendemain

Quand un développeur arrive sur un projet, son avis se résume souvent à ceci :
* le modèle de données doit être refait
* les applications doivent être refaites

Il faut alors prendre de la hauteur sur tout ceci. Si tout *marchouille* à son arrivée, c'est au prix de plusieurs années de travail, il est réellement illusoire de penser que le nouveau développeur va tout changer du jour au lendemain, car il lui faudra déjà plusieurs semaines pour assimiler toutes les règles métiers : sachant que la plupart du temps ces règles sont écrites et modifiées sur des documents ou des emails dissiminés sur plusieurs comptes utilisateurs, le développeur les découvrira bien souvent dans le code des anciennes applications.
Idem pour le modèle de données, il est intrinséquement lié au code source, chaque colonne a sa raison d'exister ou non et répond parfois à des règles énigmatiques.

44.2.5 Garder sa vieille application VS la remplacer

Quid de la TMA et du SAV ?
Malheureusement, un mauvais projet, c'est de la maintenance en plus. Là il faut aussi prendre deux minutes pour poser les choses :
- quelles sont les anomalies remontées ? Leurs fréquences ? Leurs coûts ?
* S'il suffit à l'entreprise de payer une personne 10€ pour débloquer une fois sur cent un client qui en rapporte 2000€ et que cela arrive une fois par jour sur l'application principale de la boite, quel est concrètement l'interet financier de payer un développeur 100000€/an (si on compte l'urssaf, la mutuelle, les chèques cadeaux et tout le tintoin) pour tout refaire ? Et bien en supposant que le coût de la TMA et du SAV est de 0€ (idéalement utopique), voilà deux calculs :
** cas de figure 1 : (CA - coût de SAV/TMA) = 2000*100 - 10*365 = 196 350€ de marge
-> au bout de 10 ans, en supposant une progression de 0% : 1 963 500€
** cas de figure 2 : (CA - coût du nouveau projet - coût de SAV/TMA) = 2000*100 - 100000 - 0 = 100 000€ de marge
-> au bout de 10 ans, en supposant une progression de 0% : (marge de la 1iere année + marge des 9 années suivantes) = 100 000 + 200 000*9 = 1 900 000€ de marge sur 10 ans

Conclusion ? on perd plus d'argent dans le cas de figure 2, en refaisant tout, que dans le cas 1, en payant de façon -certes parfois ingrates- quelqu'un pour maintenir le code source d'origine.

Bien sûr, si on change les chiffres de cette histoire, le cas de figure 2 peut être plus rentable. Et c'est là que c'est compliqué pour tout le monde : alors que le développeur pense ça serait forcément mieux d'avoir un projet de qualité, l'entreprise va surtout demander avant toute chose le prix que ça coutera pour le gain que ça rapportera (moins les coûts de maintenance qui ne seront jamais nulle pour autant).
Tout est donc qu'une question de mesure encore une fois, il n'y a pas de réponse magique et ferme car chaque cas est unique mais les questions qu'il faut se poser sont par contre toujours les mêmes :
* combien ça coute aujourd'hui à l'entreprise de garder cette solution technique et combien elle lui rapporte ?
* combien ça couterait de changer la solution technique et combien ça rapporterait sur le court/moyen/long terme ?

En supposant bien sûr que les clients soient toujours là, mais ça c'est encore un autre problème. (ex: le site d'ecommerce spécialiste des DVDs a bien fait de payer une nouvelle version de son site web au début des années 2010, la vente de DVDs en france décline de plus de 10% chaque année...).

44.3. Comportement 2 : améliorer l'existant au fur et à mesure, développer de nouvelles applications

Par existant, je nomme l'ensemble du code source de la société, applications Legacy ou non.

44.3.1 Faire avec l'ancien code

L'ancien code, vous pouvez toujours l'améliorer au fil de l'eau pour qu'il soit plus simple à comprendre/maintenir/modifier pour les générations futures, même dans une ancienne version du langage ou du framework.
Vous pouvez en outre être stratégique et n'améliorer que les bouts de code qui génèrent le plus de problèmes (bugs/lenteurs)

Par améliorations, voici ce que je préconise :
* réduiser le nombre de lignes de code
** en identifiant les endroits dupliqués
** en identifiant le code mort
** en créant des fonctions de refactorisation : attention à ne pas faire des choses trop complexes par ailleurs
* appliquer autant que possible les normes de code de la dernière version du langage/framework
* améliorer les performances des pages les plus critiques pour le chiffre d'affaire (ex: paiement du panier)

44.3.1.1 Qu'est ce que des choses trop complexes ?

Par complexe, j'entends que si vous remplacez quelques lignes de codes HTML facilement maintenables par n'importe qui par du code qui va génerer ces lignes avec des règles de plus en plus bizarres pour prendre en compte tous les cas, votre nouveau code, même des dizaines de fois moins long que le précédent sera plus difficile à comprendre, donc plus couteux à terme par l'entreprise.

Notamment :
* un code qui génère des formulaires HTMLs
* un code qui génère des bouts de codes PHP
* un code qui met en cache la terre entière pour optimiser sur le moment une lenteur

44.3.2 Produire du code utile

Donc que faut-il faire ? La réponse est simple : se concentrer sur du code de qualité qui *paye*.
* Faire du code de qualité : toujours garder un projet dont le code respecte les bonnes pratiques. Faire un code métier autonome du framework.
* Faire du code qui paye : travailler sur les projets qui rapporte de l'argent à l'entreprise car c'est une partie de celui-ci qui est reversé aux salariés (même dans le cas où il y a des associés vénaux).

En outre, si un développeur produit du code de qualité, il sera surement plus heureux.

44.3.3 Que faire si le métier demande une ou plusieurs nouvelles fonctionnalités

Si la nouvelle fonctionnalité demandée par le métier rentre dans le cadre d'une ancienne application, il faut savoir choisir entre deux solutions :
* faire avec les "normes" de l'application pour ajouter son bout de code
* faire un code de qualité en dehors de l'application (ex: via un web service) et l'appeler via l'application comme on peut

Si le métier demande beaucoup de nouvelles fonctionnalités, il peut s'avérer pertinent de proposer une nouvelle application qui adoptera de meilleurs principes.

Dans les deux cas, la question est toujours la même : combien cela va couter à l'entreprise sur le court/moyen/long terme ?

45. Utilisation des exceptions en PHP

Voici une interprétation de la page 15 Best Practices for Exception Handling(http://codebuild.blogspot.com/2012/01/15-best-practices-about-exception.html). Certaines best practices se complètent, il ne me semble pas nécessaire d'en citer 15.

45.1. Ne pas utiliser les exceptions pour gérer des erreurs métiers

Toutes les erreurs métiers doivent être prévues et gérées via des conditions classiques, on évitera donc d'écrire quelque chose comme catch (FunctionalException $e).

45.2. Eviter l'usage du \Exception

Le nom de l'exception doit être clair, on évite d'utiliser la classe \Exception, trop générale. Donc on évite le catch (\Exception $e) même dans le cas où on utiliserait une librairie/composant qui retournerait beaucoup d'exception (ex: Doctrine).
Evidemment, si la librairie retourne une instance d'\Exception, il n'y a pas le choix que de la catcher.cher

45.2.1 Exemples de noms clairs

* MysqlConnectionException
* NullEntityException
* EmptyFileException
* DeniedAccessException
* HttpException (Eventuellement, on peut définir des exceptions pour chaque code d'erreur HTTP)
En plus d'avoir un nom clair, chaque exception doit être correctement documentée.

45.3. Ne pas utiliser autre chose que des exceptions en cas d'erreurs techniques

On évite de retourner des nombres négatifs par exemple selon les erreurs. Mieux vaut privilégier des exceptions claires et non ambigues.

45.3.1 Exemples de mapping pour une lecture de fichier

* -1 -> FileNotFoundException
* -2 -> FileNotReadableException
* -3 -> UnsupportedFileException
* -4 -> TooBigFileException (dans le cas d'une limite à l'écriture par exemple)

45.4. Eviter de faire uniquement un throw dans un catch

Alors, ce genre de code est inutile :

try {
    // Peu importe
} catch (AnyException $e) {
    throw $e;
}

Soit le catch sert à quelque chose (on logguerait l'erreur par exemple), soit s'il ne sert à rien, il n'a pas de raison d'être.
De la même façon, ce n'est pas pertinent non plus de transformer le type d'exception en un autre type d'exception, autant laisser passer l'exception d'origine.
Eventuellement, il peut être par contre intéressant de compléter le message d'erreur de l'exception afin d'aider à la résolution du problème.

45.4.1 Eviter de ne rien faire dans le catch

Alors, ignorer l'exception, c'est souvent une fausse bonne idée. On évitera donc ce code :

try {
    // Peu importe
} catch (AnyException $e) {
    // Sifflote
}

45.5. Faire de l'héritage autant que faire ce peut

Plutôt que de faire hériter toutes ses exceptions avec \Exception, cela peut être intéressant de les réunir au sein de leurs domaines respectifs (ex: FileNotFoundException extends FileException, NotFoundHttpException extends HttpException, UnauthorizedUserException extends UserException...).

45.6. Logger les exceptions selon leur gravité

Selon les exceptions catchés, il peut être intéressant d'inscrire dans les logs les erreurs en fonction de leur gravité (DEBUG/NOTICE/FATAL).
On évite par contre de logger plusieurs fois la même erreur.

45.6.1 Exemples de niveau

DEBUG : Fichier non initialisé, Utilisateur n'existant pas encore
NOTICE : Time out sur la récupération d'une donnée non essentielle en cache par exemple
FATAL : erreur de connexion à la BDD, moteur de recherche, file d'attente

45.7. Utiliser le finally

Rarement utilisé, il peut être parfois être pratique pour libérer des ressources par exemple.

45.8. Eviter les try/catch dans des boucles

On peut catcher 1 fois mille erreurs plutôt que catcher mille fois 1 erreur.

45.9. Faire plusieurs try/catch plutôt qu'un seul

Bon cela se défend car personnellement je préfère fois un seul try/catch que plusieurs d'affilée.
L'idée est qu'il y ait peu d'instructions dans le try afin d'évaluer plus facilement d'où proviennent les erreurs.

45.10. User de code d'erreurs

Dans l'idéal, chaque exception est accompagnée d'un code d'erreur qui simplifiera la maintenance de l'application, ne serait-ce que dans l'étude statistique des problèmes courants.

45.11. Enfin, ne pas en abuser

Si pour afficher une seule page web, on risque de tomber sur l'une des 200 exceptions possibles, c'est que manifestement, la page est peut-être trop complexe.
Au-delà de 10 exceptions possibles pour un seul appel HTTP, je pense que cela devient ingérable.

46. Anti pattern Flag parameter


Le flag parameter peut être considéré comme un anti-pattern.
Le principe est de passer un argument supplémentaire (le plus souvent de type boolean) à une méthode afin que celle-ci se comporte différemment selon ce paramètre.

Par exemple, supposons cette fonction :

function render(array $data, bool $flag)
{
    if ($flag) {
        // Premier comportement.
    } else {
        // Second comportement.
    }
}

// Code appelant
$data = getData();
$flag = getFlag();
render($data, $flag);


46.1. Solution 1 : découper la fonction en deux.

La fonction render doit être découpée en deux et c'est à l'appelant de ces fonctions de faire le if/else qui convient.

function firstRender(array $data)
{
    // Premier comportement.
}
function secondRender(array $data)
{
    // Second comportement.
}

// Code appelant
$data = getData();
$flag = getFlag();

if ($flag) {
    firstRender($data);
} else {
    secondRender($data);
}


46.2. Solution 2 : fusionner les deux paramètres

Si la fonction n'est pas simple à découper (plusieurs if/else interne). Même s'il conviendrait de la refactoriser, il peut être pertinent dans certaines conditions de faire porter la valeur du flag soit dans un objet que l'on transfère à la fonction, soit dans la classe qui contient la méthode.

46.2.1 Cas 1 : via un objet

class Renderer
{
    public function render(object $object)
    {
        $flag = $object->getFlag();
        // Suite des différentes conditions.
    }
}


46.2.2 Cas 2 : via un attribut de la classe

class Renderer
{
    protected bool $flag;

    function setFlag(bool $flag)
    {
        $this->flag = $flag;
    }

    function getFlag()
    {
        return $this->flag;
    }

    function render(object $object)
    {
        $flag = $this->getFlag();
        // Suite des différentes conditions.
    }
}


46.3. Solution 3 : créer plusieurs classes et une factory ou équivalent

Chaque classe possède un comportement propre. Ce comportement contient la logique selon le cas flag=true|false.
Le rôle de la Factory sera alors de fournir la bonne instance en fonction du paramètre flag.

// Code appelant
$rendererFactory = new RendererFactory();
$renderer = $rendererFactory->create($flag);
$renderer->render($data);


🧙‍♂️️Si la fonction existe déjà, il peut être long de tout refaire, il faut à ce moment là prendre un peu de temps de réflexion quant à l'intérêt de tout refaire pour un gain immédiat souvent faible.
Privilégier donc la réfacto des morceaux de code importants (à vous de définir cette importance en fonction du contexte).


46.4. Solution type Troll

🧙‍♂️️Cette solution n'est évidemment pas conseillée.


L'idée est de créer une instance Flag et de la passer à la fonction ; ainsi nous ne passons plus un boolean et donc nous sortons de l'anti-pattern.

<?php

class Flag
{
    protected bool $value;

    public function __construct(bool $value)
    {
        $this->value = $value;
    }

    public function isFalse(): bool
    {
        return !$this->isTrue();
    }

    public function isTrue(): bool
    {
        return true === $value;
    }
}

// Code appelant
$data = getData();
$flag = new Flag(true);
render($data, $flag);

47. Liste des anti patterns

Voici la liste des anti patterns les plus populaires.

47.1. 20 antipatterns généraux

* Accidental complexity
* Action at a distance
* Boat anchor
* Busy waiting
* Caching failure
* Cargo cult programming
* Coding by exception
* Design pattern
* Error hiding
* Flag parameter
* Hard code
* Lasagna code
* Lava flow
* Loop-switch sequence
* Magic numbers
* Magic strings
* Repeating yourself
* Shooting the messenger
* Shotgun surgery
* Soft code
* Spaghetti code

47.2. 12 antipatterns POO

* Anemic domain model
* Call super
* Circle–ellipse problem
* Circular dependency
* Constant interface
* God object
* Object cesspool
* Object orgy
* Poltergeists
* Sequential coupling
* Singleton Pattern
* Yo-yo problem

47.2.1 9 antipatterns méthodologiques

* Copy and paste programming
* Golden hammer
* Invented here
* Not invented here (NIH) syndrome
* Premature optimization
* Programming by permutation
* Reinventing the square wheel
* Silver bullet
* Tester-driven development

48. Anti Pattern : LoopSwitch


48.1. Exemple

Voici un exemple d'implémentation de cet anti pattern.

<?php

namespace App\Core;

/**
 * Class LoopSwitch
 */
class LoopSwitch
{
    protected const FIRST_STEP = 'first_step';
    protected const SECOND_STEP = 'second_step';

    /**
     * @var string[]
     */
    protected array $steps = [
        self::FIRST_STEP,
        self::SECOND_STEP,
    ];

    /**
     * Executes All the steps.
     */
    public function executeAllSteps()
    {
        foreach($this->steps as $step) {
            $this->executeStep($step) ;
        }
    }

    /**
     * @param string $step
     */
    public function executeStep(string $step)
    {
        switch ($step) {
            case self::FIRST_STEP:
                echo self::FIRST_STEP;
                break;
            case self::SECOND_STEP:
                echo self::SECOND_STEP;
                break;
        }
    }
}

49. Prototype

49.1. Introduction

Le Prototype est un design pattern très simplement accessible en PHP puisque l'usage de la méthode __clone est une application de celui-ci.

Le Prototype est la capacité d'un Object de se cloner en un nouvel Object qui hérite de l'état de l'Object cloné.

A noter que le __clone crée une nouvelle référence pour l'Object copié.

Dans le cas où un Object fait référence à d'autres Objects (par exemple un Attribute "Color $color" ), cette référence est copiée, ce qui peut être problématique si l'on modifie la valeur de l'Object référencé.

49.2. Le problème

Supposons qu'on a une Class Pencil qui aurait un Attribute Color :

Pencil.php
<?php
class Pencil implements PencilInterface
{
    protected ColorInterface $color;

    public function __construct(ColorInterface $color) 
    {
        $this->color = $color;
    }

    public function getColor(): ColorInterface 
    {
        return $this->color;
    }
}

PencilInterface.php
<?php
interface PencilInterface 
{
    public function getColor(): ColorInterface;
}

ColorInterface.php
<?php
interface ColorInterface 
{
    public function getValue(): string;
}

Color.php
<?php
class Color implements ColorInterface 
{
    public function __construct($value)
    {
        $this->value = $value;
    }

    public function setValue(string $value)
    {
        $this->value = $value;
    }

    public function getValue(): string
    {
        return $this->value;
    }
}


Voici un comportement problématique :

    $color = new Color('red');
    $firstPencil = new Pencil($color);
    $clonedPencil = clone $firstPencil;
    $color->setValue('green');
    echo $clonedPencil->getColor()->getValue(); // -> 'green'
    echo $firstPencil->getColor()->getValue(); // -> 'green'


Ici, les deux instances de Pencil se retrouvent avec une couleur ayant la valeur verte. Ce que l'on ne souhaitait pas forcément : on souhaite avoir un Pencil ayant une Color 'green' et l'autre ayant une Color 'red'.
La seule façon de résoudre ce problème est de surcharger la méthode __clone afin de cloner également l'instance de Color (et donc obtenir une nouvelle référence différente de la première).

49.3. Solution

Il faut réécrire la méthode __clone de cette façon :

Pencil.php
<?php
class Pencil implements PencilInterface
{
    protected ColorInterface $color;

    public function __construct(ColorInterface $color) 
    {
        $this->color = $color;
    }

    public function getColor(): ColorInterface 
    {
        return $this->color;
    }

    public function __clone() 
    {
        $this->color = clone $this->color;
    }
}


Se faisant, si l'on réexecute le bout de code précédent, on obtient ceci

    $color = new Color('red');
    $firstPencil = new Pencil($color);
    $clonedPencil = clone $firstPencil;
    $color->setValue('green');
    echo $clonedPencil->getColor()->getValue(); // -> 'red'
    echo $firstPencil->getColor()->getValue(); // -> 'green'

Le premier crayon a bien hérité d'une couleur verte, tandis que le second a bien une couleur qui a été modifiée en rouge.

49.4. Les ValueObjects

Le ValueObject est un Object immutable, c'est à dire que ces propriétés sont initialisés qu'à un seul moment, la grande majorité du temps dans le constructeur ; il est aussi possible de faire des setters qui ne settent qu'une seule fois la valeur, ce qui peut potentiellement produire un effet de bord selon le test effectué.

49.5. Setter d'un immutable

Voici à quoi pourrait ressembler un tel setter. Reprenons la Class Pencil :
<?php
class Pencil implements PencilInterface
{
protected ?ColorInterface $color = null;

public function __construct(ColorInterface $color)
{
$this->color = $color;
}


public function setColor(?ColorInterface $color = null): PencilInterface
{
if (\is_null($this->color)) {
$this->color = $color;
}

return $this;
}

public function getColor(): ColorInterface
{
return $this->color;
}
}


Ici, on sette la couleur une seule et unique fois, mais étant donné que l'Attribute color peut également être null, il est tout à fait possible de passer une première fois dans la méthode, setter $color à null, puis repasser une autre fois et setter $color avec une instance implémentant ColorInterface.
Le principe d'un Object immutable est de ne plus être modifié après sa création, ce qui potentiellement peut toujours être le cas ici.
Eventuellement, on pourrait rajouter un Attribute indiquant si l'état est fixe ou non, mais cela deviendrait compliqué à gérer s'il fallait identifier pour chaque Attribute si son setter a été ou non appelé. En outre, il faudrait également garantir que cet Attribute n'est setté qu'une seule fois.

Donc la solution de passer par des setters plutôt que par le constructeur n'est pas une bonne idée.

49.6. Comment cloner un Object possédant un Attribute ValueObject ?

Il est difficile de lier les concepts de Prototype et de ValueObject car il faudrait créer une méthode clone ayant des paramètres similaire à ceux du __construct.
Dans le cas de l'Attribute Color, si l'on considère que c'est un ValueObject, il ne serait plus possible de modifier la Color du Pencil cloné, ce qui serait déroutant.

Donc si l'on souhaite cloner un Object possédant un Attribute ValueObject, il faut accepter que cet Attribute sera immutable d'un clone à l'autre.

49.7. Factory+Pool

Pour créer un Pencil avec une Color immutable, on peut passer par une Factory, voire mélanger le pattern design Pool et Factory afin de conserver les différents Pencil créés.

On peut envisager de n'avoir qu'une seule Factory nommé PencilFactory (on pourrait éventuellement avoir une deuxième Factory nommée ColorFactory qui stockerait les différents instances de Color).

Ici, la PencilPoolFactory garde en mémoire les différentes instance de Color créées puis fabrique et retourne des instances de Pencil. Etant donné qu'il change d'état, il n'est donc pas possible de passer par une méthode statique.

<?php
class PencilPoolFactory
{
    /**
    * ColorInterface[]
    */
    protected array $colors = array();

    public function has(string $key)
    {
        return isset($this->colors[$key]);
    }

    public function get(string $colorValue): ColorInterface
    {
        return $this->colors[$colorValue];
    }
  
     public function add(ColorInterface $color, string $colorValue)
     {
         $this->colors[$colorValue] = $color;
     }

    public function create(string $colorValue)
    {
        if (!$this->has($colorValue)) {  
            $this->add(new Color($colorValue), $colorValue);
        }

        return new Pencil($this->get($colorValue));
    }
}


Voici comment l'utiliser :

    $pencilPoolFactory = new PencilPoolFactory();
    $redPencil = $pencilPoolFactory->create('red');
    $greenPencil = $pencilPoolFactory->create('green');

    echo $redPencil->getColor()->getValue(); // 'red'
    echo $greenPencil->getColor()->getValue(); // 'red'


🧙‍♂️️Le PencilPoolFactory mélange deux design patterns mais cela produit un mauvais pattern (le mot antipattern est un peu trop populaire et désigne aujourd'hui des choses eux-mêmes populaires).
D'une première raison : ce PencilPoolFactory fabrique des instances de Color ET des instances de Pencil, il a donc deux responsabilités, ce n'est pas la faute de la combinaison des deux design patterns.
D'une deuxième raison : le PencilPoolFactory garde en mémoire les instances de Color, ce qui lui donne une nouvelle responsabilité car l'utilisateur sera tenté de faire également un $pencilPoolFactory->get('color').


49.7.1 Résolution du problème

Il faut découper le PencilPoolFactory en trois : PencilFactory, ColorFactory et PencilPool. Chacune de ces Classs aura une seule responsabilité.

49.7.1.1 PencilPool avec un Attribute $colorFactory

* Le PencilFactory aura un Attribute Singleton : $pencilPool.
* Le PencilPool aura un Attribute Singleton : $colorFactory.

PencilFactory.php
<?php
class PencilFactory
{
    PencilPoolInterface $pencilPool;

    public function __construct(
        PencilPoolInterface $pencilPool
    ) 
    {
        $this->pencilPool = $pencilPool
    }

    public function create(string $colorValue): PencilInterface
    {
        $color = $this->pencilPool->get($colorValue);

        return new Pencil($color);
    }

    protected function getColor(string $colorValue): ColorInterface
    {
        return $this->pencilPool->get($colorValue);
    }
}



Cette solution est dérangeante. Le PencilPool dispose d'un Attribute autre que celui pour lequel il est dédié, ce qui est déroutant car il stocke des références de ColorInterface ET de PencilFactoryInterface.

La solution inverse semble plus pertinente : le PencilFactory possède un Attribute $pencilPool qui ferait office de stockage des nouvelles instances.

49.7.1.2 ColorFactory avec un Attribute $pencilPool

Le PencilPool se réduit ici à son strict nécessaire : il stocke des instances implémentant ColorInterface

PencilPool.php
<?php
class PencilPool implements PencilPoolInterface
{
    /**
    * ColorInterface[]
    */
    protected array $colors = array();

    public function has(string $key)
    {
        return isset($this->colors[$key]);
    }

    public function get(string $colorValue): ColorInterface
    {
        return $this->colors[$colorValue];
    }
  
    public function add(ColorInterface $color, string $colorValue)
    {
        $this->colors[$colorValue] = $color;
    }
}

Et le PencilFactory possède un Attribute $pencilPool.

PencilFactory.php
<?php
class PencilFactory implements PencilFactoryInterface
{
    protected PencilPool $pencilPool;
    
    public function create(string $colorValue)
    {
        if (!($pencilPool->has($colorValue)) {
            $color = new Color($colorValue);
            $pencilPool->set($color, $colorValue);
        } else {
            $color = $pencilPool->get($colorValue);
        }

        return $color;
    }
}

La PencilFactory demande à sa $pencilPool si la Color existe, auquel cas elle le retourne. Si ce n'est pas le cas, elle la crée et le transmet au pencilPool pour que ce dernier le stocke.

Eventuellement, on pourrait imaginer une nouvelle Class plus complexe, avec un nom un peu ambigue comme PencilManager.

Cette Class ferait le lien entre le PencilFactory, le ColorFactory et le PencilPool.
Cela garantirait que les trois Class seraient simples et immutables (on ne les modifierait plus une fois conçues).

Sauf qu'on imagine bien que le PencilManager finirait par avoir plusieurs responsabilités, même si elles sont déléguées à d'autres Class, ce qui n'est en soi pas un problème si on fait en sorte que ce Manager ne représente que la fusion des trois autres.
Ce ne serait qu'une Facade, qui ne devrait faire qu'une chose : retourner un Pencil en fonction d'une valeur de couleur.




50. Le design pattern Singleton et l'implémentation des services

Avec les frameworks modernes, on passe notre temps à manipuler des Singletons car tout ce qu'on nomme service en est en fait un.

Un singleton n'est rien d'autre qu'une classe que l'on ne peut instancier qu'une et une seule fois par exécution. Si l'on désire créer une nouvelle instance et qu'il en existe déjà une, c'est celle-ci qui sera utilisée, magique donc !

50.1. La classe Singleton

50.1.1 Schema DML


50.1.2 Exemple de la classe Singleton

Voici une façon d'écrire une classe qui implémente le design pattern Singleton :

<?php
class Singleton
{
    private static $mySelf = null;

    private function __construct(){} // Empêche toute création d'instance.
    private function __clone(){} // Empêche toute copie d'instance.

    public static function getInstance()
    {
        if (self::$mySelf === null) {
            self::$mySelf = new Singleton();
        }
        
        return self::$mySelf;
    }
}

50.1.3 Exemple d'utilisation (attention, ça se complique !)

A l'usage, lorsque l'on cherche à créer deux instances, voici ce qu'il se passe:

<?php
$firstSingleton = Singleton::getInstance();
$secondSingleton = Singleton::getInstance();

50.1.4 Pourquoi l'usage de self et du static ?

Puisque que la classe n'est pas instanciée au moment de l'appel à getInstance, $this n'existe pas encore. Et comme $this n'existe pas, le seul moyen de faire appel à l'attribut mySelf est de passer par du static ::.

50.2. La classe SingletonMaker

50.2.1 Introduction

C'est tout de même très étrange comme façon de créer une instance, c'est un peu trop fantastique ce qu'il se passe, non ? On dirait que l'on hack la façon dont on doit utiliser les classes, notamment, il me semble vraiment étrange pour une classe de s'autocréer.
On pourrait éventuellement passer par un créateur de Singleton pour gérer tout ceci de façon plus claire, je vous propose donc de créer le SingletonMaker.

50.2.2 Exemple de la classe SingletonMaker

A noter que la classe aurait pu s'appeler SingletonFactory, mais elle s'appelle SingletonMaker pour éviter l'ambiguité avec le design pattern Factory, dont le code ci dessous n'est pas représentatif.

class Singleton
{

}

class SingletonMaker
{
    private static $singleton = null;

    public static function getSingleton()
    {
        if (self::$singleton === null) {
            self::$singleton = new Singleton();
        }

        return self::$singleton;
    }
}

C'est déjà un peu plus normal ce qu'il se passe ici, on passe par une classe qui nous retourne le Singleton en le créant si besoin.

50.2.3 Exemple d'utilisation

Rien de très différent de la fois d'avant.

<?php
$firstSingleton = SingletonMaker::getSingleton();
$secondSingleton = SingletonMaker::getSingleton();

50.3. Notre gestionnaire de services - la classe ServiceManager

Quelque part, nous venons de créer un gestionnaire de services, si on renomme un peu les choses, voici ce qu'on pourrait obtenir :

50.3.1 Proposition d'implémentation du ServiceManager

En renommant Singleton en MyFirstService, en créant une seconde classe MySecondService et en renommant le SingletonMaker par ServiceManager, on pourrait écrire ceci :

class MyFirstService
{

}

class MySecondService
{

}

class ServiceManager
{
    private static $services = [];

    public static function get(string $serviceClassName)
    {
        if (!isset(self::$services[$serviceClassName])) {
            self::$services[$serviceClassName] = new $serviceClassName();
        }

        return self::$services[serviceClassName];
    }
}


A l'usage, voici comment on récupérerait nos services :

$myFirstService = ServiceManager::get('MyFirstService');
$mySecondService = ServiceManager::get('MySecondService');

50.3.2 Appelation de services plus abstraites

Et eventuellement faire une appelation de services différente que l'on recalculerait à la volée.

Ex : 'myFirstService' comme nom du service et new ucfirst("myFirstService")() pour créer l'instance (pour rappel le ucfirst met en majuscule la première lettre d'une chaine de caractères).

class MyFirstService
{

}

class ServiceManager
{
    private static $services = [];

    public static function get(string $serviceName)
    {
        $serviceClassName = ucfirst($serviceName);

        if (!isset(self::$services[$serviceName])) {
            self::$services[$serviceName] = new $serviceClassName();
        }

        return self::$services[$serviceName];
    }
}


A l'usage, on pourrait l'appeler comme ceci :

$myService = ServiceManager::get('myFirstService');

50.3.2.1 Apparté

Derrière, on peut appeler ça un containeur de services, puis déclarer dans un fichier de configuration yml, xml ou ce que l'on souhaite le nom des services publics (haha).

51. Static Factory

51.1. Introduction

FactoryMethod/AbstractFactory/StaticFactory/SimpleFactory : peu importe le nom qu'on donne à ces designs patterns, il faut surtout comprendre que le but d'une classe suffixée Factory est celui-ci : construire et retourner des objets.
Que cette classe Factory étende une AbstractFactory, qu'elle contienne une ou plusieurs méthodes de fabrication, qu'elle soit elle même générée par une autre Factory, l'essentiel est qu'elle crée des instances.
En outre, si le Factory a ce rôle de constructeur, il paraitrait logique que tous les 'new' ne soient présents que dans de telles classes dans tout le code source du projet.

Les méthodes de création des classes suffixées Factory sont justement nommées FactoryMethod. "(create(...)/factory(...)/make(...)")


51.2. Intention

Fournir une interface permettant de créer des familles d'objets liés ou dépendants sans spécifier leurs classes concrètes. A la différence de l'AbstractFactory qui définit des Factory ayant plusieurs méthodes de fabrication, la StaticFactory définit des Factory qui n'ont qu'une seule méthode statique de fabrication pour un ensemble d'objets.

🧙‍♂️️Ce que je comprends plutôt de ce qu'est le StaticFactory :
A la différence de l'AbstractFactory, on ne définit ici qu'une seule classe Factory ; étant donné qu'il n'y a qu'une seule classe Factory, il n'est donc pas nécessaire de mettre du code dans une classe abstraite (donc le fichier 'AbstractSomethingFactory.php' n'existerait pas). Le design pattern StaticFactory est un cas particulier de l'AbstractFactory.


51.3. Exemple concret

Supposons que l'on souhaite afficher à l'écran le nom du repas à prendre en fonction du moment de la journée (par exemple pour une personne qui n'aurait plus toute sa tête). Pour simplifier, on ne considère que trois types de repas : le petit déjeuner, le déjeuner et le diner.

51.3.1 Code procédural

En procédural, voici ce que l'on pourrait écrire :

index.php
<?php
$currentHour = (int) date('H');

if ($currentHour < 12) {
    echo 'breakfeast';
} elseif ($currentHour < 16) {
    echo 'lunch';
} else {
    echo 'dinner';
}

Ici, il y a un problème fondamental : la logique heure/type de repas n'est pas récupérable en l'état dans les autres fichiers de l'application, il faudra alors dupliquer ce code. Si un nouveau repas est introduit (comme le petit déjeuner), il faudra chercher dans l'ensemble du code cette logique pour la changer.

Une fonction aurait tout sa place ici.
Par exemple :

<?php
    function getMealName($currentHour): string
    {
        if ($currentHour < 12) {
            $mealName = 'breakfeast';
        } elseif ($currentHour < 16) {
            $mealName = 'lunch';
        } else {
            $mealName = 'dinner';
        }

        return $mealName;
    }
    echo getMealName((int) date('H'));

Cette fonction résout le problème principal : la logique heure/type de repas est à un seul endroit.
Là où cela se complique, c'est si l'on souhaite retourner autre chose que le nom du repas, par exemple les horaires de celui-ci, on pourrait éventuellement faire une deuxième fonction comme celle-ci :

    function getMealTime($currentHour): string
    {
        if ($currentHour < 12) {
            $mealTime = '0h - 12h';
        } elseif ($currentHour < 16) {
            $mealTime = '12h - 16h';
        } else {
            $mealTime = '> 16h';
        }

        return $mealTime;
    }
    echo getMealTime((int) date('H'));

Mais cette fonction génère à nouveau un problème de duplication.
Non seulement on duplique à nouveau la logique heure/type de repas mais s'il faut rajouter un type de repas, il faudra modifier les deux fonctions.

La solution ici est de renvoyer une donnée plus complexe qui contiendrait plusieurs informations. Cette donnée pourrait contenir à la fois le nom du repas mais également les horaires de celui-ci. Il existe des solutions pour cela : on peut retourner un tableau, une structure particulière (ex: json/xml/yaml) ou bien un Objet. Le tableau ou la structure pose une difficulté : le client de la fonction doit connaitre le nom des clefs d'accès aux différentes informations. Dans le cas d'un Objet, le client a accès aux méthodes publiques de celui-ci pour accéder à une représentation des différentes informations.

51.3.2 En POO

51.3.3 Création des modèles Meal

En POO, on pourrait représenter chaque type de repas de la façon suivante :

MealInterface.php
<?php 

namespace App\Model\Meal;

interface MealInterface
{
    public function getName();
}

AbstractMeal.php
<?php
namespace App\Model\Meal;

abstract class AbstractMeal implements MealInterface
{
    protected string $name;

    public function getName(): string
    {
        return $this->name;
    }
}

Breakfeast.php
<?php
namespace App\Model\Meal;

class Breakfast extends AbstractMeal implements MealInterface
{
    public function __construct()
    {
        $this->name = 'breakfast';
    }
}

Lunch.php
<?php
namespace App\Model\Meal;

class Lunch extends AbstractMeal implements MealInterface
{
    public function __construct()
    {
        $this->name = 'lunch';
    }
}

Dinner.php
<?php
namespace App\Model\Meal;

class Dinner extends AbstractMeal implements MealInterface
{
    public function __construct()
    {
        $this->name = 'dinner';
    }
}

51.3.4 Mise en application sans Factory

Maintenant que les différentes classes sont prêtes, on peut les utiliser :

MealController.php
<?php 
    namespace App\Meal\Controller;

    use App\Meal\Model\Breakfast;
    use App\Meal\Model\Lunch;
    use App\Meal\Model\Dinner;

    class MealController 
    {
        public function index()
        {
            $currentHour = (int) date('H');
            
            if ($currentHour < 12) {
                $meal = new Breakfast();
            } elseif ($currentHour < 16) {
                $meal = new Lunch();
            } else {
                $meal = new Dinner();
            }

            echo $meal->getName();
        }
    }


Ici, le Controller doit avoir connaissance des différentes classes pour faire son affichage. Ce qui pose un problème dans le cas d'une modification des types de repas : il faut modifier l'ensemble des fichiers qui manipulent les classes concrètes (là où il y a des 'use').

Le but de la Factory est d'éviter cela afin que le Controller n'ait plus à gérer les différents modèles. Tout ce qu'il doit savoir est qu'il manipule une instance qui implémente l'interface MealInterface. Cette interface a une méthode getName(), commune à toutes les classes qui l'implémentent.

51.3.5 Avec l'Abstract Factory

Voici une implémentation possible de la classe MealFactory, elle reprend la logique du MealController :

MealFactory.php
<?php
    namespace App\Meal\Factory;

    use App\Meal\Model\MealInterface;
    use App\Meal\Model\Breakfast;
    use App\Meal\Model\Lunch;
    use App\Meal\Model\Dinner;

    class MealFactory implements MealFactoryInterface
    {
        public static function create(int $hour): MealInterface
        {
            if ($hour < 12) {
                $meal = new Breakfast();
            } elseif ($hour < 16) {
                $meal = new Lunch();
            } else {
                $meal = new Dinner();
            }

            return $meal;
        }
    }


Elle implémente l'interface MealFactoryInterface. Cette interface permettra dans le cas où l'on souhaite créer une deuxième MealFactory (par exemple pour un pays où les repas seraient différents) de mettre en place le mécanisme d'AbstractFactory (ie. plusieurs classes de type MealFactory (FrenchMealFactory/AfricanMealFactory)).

MealFactory.php
<?php
    namespace App\Meal\Factory;

    use App\Meal\Model\MealInterface;

    interface MealFactoryInterface
    {
        public function create(int $hour): MealInterface;
    }


Voici désormais ce que fera le MealController :

MealController.php
<?php 
    namespace App\Meal\Controller;

    use App\Meal\Factory\MealFactory;

    class MealController
    {
        public function index()
        {
            $mealFactory = MealFactory::create((int) date('H'));
            echo $meal->getName();
        }
    }


🧙‍♂️️L'usage des méthodes static est assez déconseillée car le Controller a ici une dépendance avec la classe concrète de la Factory. Si on souhaite utiliser une autre MealFactory, il faudra modifier dans l'ensemble du code cet usage. Mieux vaut que MealFactory implémente l'interface MealFactoryInterface. On injectera ensuite la Factory dans le constructeur des classes qui en dépendent.



51.4. Sources

📖️️https://designpatternsphp.readthedocs.io/en/latest/Creational/StaticFactory/README.html

📖️️https://waytolearnx.com/2020/02/design-patterns-static-factory-en-php.html

52. Le design pattern Decorator

52.1. Schema DML


53. Le design pattern Observer

53.1. Schema DML



53.2. Implementation

Ce DP introduit deux notions : l'Observer et l'Observable.
L'Observable doit envoyer une notification aux Observers qui lui sont rattachés pour leur indiquer que l'une de ces données a été changée.

La classe Observable doit donc gérer à la fois son modèle et la liste des Observers (qui sont des attributs de la classe).
Le sujet observé doit donc être au courant que les observateurs existent.

Voici une implémentation classique :

class Subject
{
    protected Observer[] $observers;
    public function __construct(Observer[] $observers) {
        $this->observers = $observers;
    }

    public function doSomething()
    {
        $this->observer->notify($this);
    }
}
abstract class Observer
{
    public function notify(Subject $subject)
    {
         // Fais quelque chose
    }
}
class MyObserver extends Observer
{

}


Une façon différente de procéder serait d'utiliser des EventsListeners : on crée une classe qui met à jour le modèle et qui notifie une liste de Listeners en fonction d'une typologie d'évènement (par exemple des changements d'état).



54. Immutable MVC en PHP version 2019


Ceci est une proposition de traduction de la page Immutable MVC: MVC In PHP 2019 Edition (https://r.je/immutable-mvc-in-php) de Tom Butler, que j'estime être très doué.
A noter que je n'ai pas trouvé quelle était la licence sur le contenu de son article, il est bien entendu que cette page sera supprimée si la copie de l'article original est interdite.

-- Début de la traduction --

54.1. MVC Revisité

Les articles les plus populaires de ce site Web concernent le MVC, dont le plus ancien a maintenant près de dix ans. Beaucoup de choses ont changé au cours de cette période, et ma propre expérience s'est également enrichie. Ma précédente série d'articles sur les "accidents vasculaires cérébraux" s'est en quelque sorte éteinte parce que je voulais revenir en arrière et peaufiner les exemples des articles précédents.

Le PHP a beaucoup changé depuis que j'ai écrit ces articles. Je vais prendre un nouveau départ et donner des exemples de code plus propres, plus complets et plus modernes.

Neuf ans se sont écoulés depuis que j'ai écrit le premier article sur les MVC ici et j'ai quelques observations à faire à ce sujet en regardant en arrière :

L'écosystème du PHP n'a pas changé. Les gens confondent toujours l'approche "contrôleur au lieu de médiateur" et MVC. Si la situation a empiré parce que les cadres populaires sont maintenant si bien intégrés, essayer de faire quelque chose de différent semble tout simplement mal.
Les points que j'ai soulevés sont toujours valables. Donner à la vue l'accès au modèle présente des avantages significatifs que je ne répéterai pas ici. Jetez un coup d'œil à mon précédent article si vous êtes intéressé.
Les exemples de code que j'ai donnés ne sont pas assez clairs ou complets. J'ai passé plus de temps à me concentrer sur les concepts qu'à donner des exemples de code. Je vais rectifier cela ici.
En plus d'écrire des exemples plus complets, il est temps de faire une mise à jour avec les nouvelles fonctionnalités de PHP et les tendances de programmation comme l'immutabilité. Je vais vous montrer comment écrire une structure MVC complètement immuable. Ensuite, je vous montrerai des contrôleurs réutilisables et un système de routage.

Outre l'utilisation de nouvelles fonctionnalités de PHP telles que l'indication du type de retour (ndlr, PHP 7.0), les deux principaux changements que j'ai apportés à mon style de codage sont l'immutabilité et le fait de privilégier les arguments par rapport aux constructeurs ayant des propriétés connexes.

54.2. Hello World

Voici une démonstration du trinome MVC en immutable à travers le fichier mvc.php dont voici le contenu :

class Model
{
    public $text;
    
    public function __construct() 
    {
        $this->text = 'Hello world!';
    }
}

class View
{
    private $model;

    public function __construct(Model $model) 
    {
        $this->model = $model;
    }

    public function output()
    {
        return '<a href="mvc.php?action=textclicked">' . $this->model->text . '</a>';
    }
}

class Controller
{
    private $model;

    public function __construct(Model $model)
    {
        $this->model = $model;
    }

    public function textClicked()
    {
        $this->model->text = 'Text Updated';
    }
}

$model = new Model();

// It is important that the controller and the view share the model.
$controller = new Controller($model);

$view = new View($model);

if (isset($_GET['action'])) $controller->{$_GET['action']}();
echo $view->output();


54.3. Le modèle

Dans cet exemple, le modèle est mutable mais les autres composants ne le sont pas. Tout d'abord, rendons le modèle immuable :

class Model
{
    private $text;

    public function __construct($text = 'Hello World')
    {
        $this->text = $text;
    }

    public function getText()
    {
        return $this->text;
    }
    
    public function setText($text)
    {
        return new Model($text);
    }
}

Il n'est plus possible de modifier l'état du modèle une fois qu'il a été instancié. L'appel de la méthode setText crée une nouvelle instance. Tout endroit où l'instance originale est référencée ne verra pas de changement dans l'état du modèle. Le contrôleur et la vue devront être mis à jour pour utiliser cette nouvelle classe de modèle.

Auparavant, le modèle et le contrôleur partageaient une instance de modèle. Le contrôleur modifiait l'état du modèle et la vue lisait l'état de l'instance de modèle. Maintenant que le modèle est immuable, nous ne pouvons plus compter sur le fait que la vue et le contrôleur partagent la même instance de modèle. Au lieu de cela, l'action du contrôleur retournera une instance de modèle qui sera ensuite transmise à la vue.

54.4. Le contrôleur

Au lieu de mettre à jour l'état d'une instance de modèle existante et mutable, le contrôleur renvoie une nouvelle instance de modèle après avoir effectué ses modifications :

class Controller
{
    public function textClicked(Model $model): Model
    {
        return $model->setText('Text Clicked');
    }
}


Plutôt que de m'appuyer sur les constructeurs et les propriétés, j'ai choisi d'utiliser des arguments pour faire passer le modèle qui va être mis à jour. L'action du contrôleur reçoit maintenant une instance de modèle immuable et renvoie une nouvelle instance avec les modifications nécessaires.

Il est à noter que cela ne fait pas partie de MVC, l'utilisation d'arguments de constructeur avec des propriétés liées ou d'arguments à l'action de contrôleur ont le même effet. Mais en évitant les constructeurs, il n'est plus nécessaire de conserver le modèle comme une propriété et le contrôleur est considérablement simplifié. Moins de code pour faire le même travail est toujours une bonne chose.

54.5. La vue

La vue nécessite peu de modification pour arriver au même résultat (plus besoin de l'attribut publique).

class View
{
    public function output(Model $model)
    {
        return '<a href="mvc.php?action=textClicked">' . $model->getText() . '</a>';
    }
}

L'avantage de faire passer le modèle en argument est que une seule instance de View permet d'afficher autant de modèles que l'on souhaite.

54.6. Mise en place du trinome

Il ne reste plus qu'à instancier tout ce petit monde, plus besoin de passer le modèle aux constructeurs de la View et du Controller.

<?php
$model = new Model();
$controller = new Controller();
$view = new View();

if (isset($_GET['action']))
{
    $model = $controller->{$_GET['action']}($model);
}

echo $view->output($model);


Dans cette version immuable, le modèle est passé au contrôleur qui construit une nouvelle instance du modèle qui est ensuite passé à la vue.

54.7. Conclusion

Cette implémentation immuable de MVC présente certains avantages par rapport à la version mutable :

L'état est mieux géré de sorte que l'application ne souffre pas d'une action à distance où le changement d'un objet à un endroit (dans le contrôleur) provoque ensuite des changements dans un composant apparemment sans rapport (la vue).
Il y a moins d'État dans l'ensemble. Il n'y a plus de références aux différents objets en plusieurs endroits. Le contrôleur et la vue n'ont plus de référence au modèle, on leur donne une instance avec laquelle travailler au moment où ils en ont besoin, pas avant.

-- Fin de la traduction --

Bien évidemment, ce code ne doit pas se retrouver de cette façon dans votre application car le paramètre HTTP action doit être vérifié (ie : on contrôle que l'utilisateur a le droit ou non de faire l'action).

54.8. Vue d'artiste

Attention : le schéma suivant ne représente pas vraiment la beauté conceptuelle derrière le MVC.

55. DML 0.1 : Dot Modeling Language


Le DML est une représentation simplifiée du diagramme de classe UML à base de petits ronds.

Chaque petit rond représente un type d'élément de la POO (interface, classe, trait, classe abstraite).

Cette représentation a pour objectif de détecter les problèmes de dépendances entre les classes de notre projet afin d'éviter des incohérences de conception et de produire du code spaghetti.

A la différence du diagramme de classe, les types de liens (extends, implements) ne sont pas représentés car ces liens sont induits par les éléments liés.
Une interface liée par une classe l'est forcément pas un lien d'implementation (implements). De même qu'une classe liée par une autre classe l'est forcément par un lien d'héritage (extends), ceci étant vrai pour les classes abstraites.
Le DML s'occupe uniquement de représenter les use entre les classes.

55.1. Légende du DML

L'idée du DML est d'offrir aux développeurs un outil simple pour prendre des décisions de conception. Cette représentation est plus rapide à mettre en oeuvre sur un tableau noir ou blanc, qu'il soit numérique ou non.



* les interfaces sont représentées par des ronds vides ;
* les classes abstraites sont représentées par des ronds marqués par un point central ou par des hachures ;
* les classes sont représentées par des ronds pleins ;
* les traits sont représentés par des ronds contenant un trait ou des rayures ;
* la dépendance entre les classes est marquée par un trait simple dont la longueur peut être nulle si le lien entre deux éléments est évident.

55.2. Implémentation en DML

En DML, une classe qui implémente une interface peut être représentée de la façon suivante.
Le lien est implicitement un implements.

Dans le cas où l'implémentation est classique (MyClass extends MyInterface), on peut coller les deux ronds ensemble.

55.3. Héritage en DML

En DML, une classe qui hérite d'une classe abstraite peut être représentée de la façon suivante.
Le lien est implicitement un extends.


55.4. Trait en DML

En DML, une classe qui utilise un trait peut être représentée de la façon suivante.
Le lien est un use.


55.5. Représentation générale.

Voici une réprésentation générale de tous les éléments. Il est tout à fait possible de mélanger les représentations courtes et longues des dépendances entre les classes.


Voici la même représentation avec cette fois-ci des liens de longueur nulle.

Le nom des éléments peut disparaitre, seuls les liens entre ceux-ci existent.
* Il est envisageable de changer la couleur de chaque élément pour savoir ce que représente chaque rond. (Par exemple, une couleur par composant/vendor/bundle)
* Il est possible aussi de changer la taille des ronds selon l'importance de la dépendance lors de la conception (par exemple, il n'est pas nécessaire d'afficher la dépendance avec la classe Request dans une classe Controller, au contraire de la dépendance avec un Service (Repository/WsClient/Manager/Provider/Transformer...), si on souhaite le faire, le rond peut être à peine visible).


55.6. DML centrée

Avec le DML, il est possible de représenter une seule classe afin de voir le nombre de dépendance qu'elle a. Plus le nombre de ronds qui gravitent autour du rond principal est grand, plus la classe est difficile à maintenir.


56. L'héritage


Une classe SubClass peut hériter d'une autre classe MainClass.
Elle hérite ainsi de toutes les méthodes et de tous les attributs.
Ceci évite de dupliquer du code et permet de faire de l'override en redéfinissant ci-besoin les attributs/méthodes.

56.1. Code PHP


interface MainInterface 
{
    public function method();
}

class MainClass implements MainInterface 
{
    public function method()
    {
    }
}

class FirstSubClass extends MainClass
{
    public function method()
    {
        // Another implementation of the method.
    }
}

class SecondSubClass extends MainClass
{
    public function method()
    {
        // Another implementation of the method.
    }
}


56.2. Schéma DML



57. Immutable MVC en PHP version 2019


Ceci est une proposition de traduction de la page Immutable MVC: MVC In PHP 2019 Edition (https://r.je/immutable-mvc-in-php) de Tom Butler, que j'estime être très doué.
A noter que je n'ai pas trouvé quelle était la licence sur le contenu de son article, il est bien entendu que cette page sera supprimée si la copie de l'article original est interdite.

-- Début de la traduction --

57.1. MVC Revisité

Les articles les plus populaires de ce site Web concernent le MVC, dont le plus ancien a maintenant près de dix ans. Beaucoup de choses ont changé au cours de cette période, et ma propre expérience s'est également enrichie. Ma précédente série d'articles sur les "accidents vasculaires cérébraux" s'est en quelque sorte éteinte parce que je voulais revenir en arrière et peaufiner les exemples des articles précédents.

Le PHP a beaucoup changé depuis que j'ai écrit ces articles. Je vais prendre un nouveau départ et donner des exemples de code plus propres, plus complets et plus modernes.

Neuf ans se sont écoulés depuis que j'ai écrit le premier article sur les MVC ici et j'ai quelques observations à faire à ce sujet en regardant en arrière :

L'écosystème du PHP n'a pas changé. Les gens confondent toujours l'approche "contrôleur au lieu de médiateur" et MVC. Si la situation a empiré parce que les cadres populaires sont maintenant si bien intégrés, essayer de faire quelque chose de différent semble tout simplement mal.
Les points que j'ai soulevés sont toujours valables. Donner à la vue l'accès au modèle présente des avantages significatifs que je ne répéterai pas ici. Jetez un coup d'œil à mon précédent article si vous êtes intéressé.
Les exemples de code que j'ai donnés ne sont pas assez clairs ou complets. J'ai passé plus de temps à me concentrer sur les concepts qu'à donner des exemples de code. Je vais rectifier cela ici.
En plus d'écrire des exemples plus complets, il est temps de faire une mise à jour avec les nouvelles fonctionnalités de PHP et les tendances de programmation comme l'immutabilité. Je vais vous montrer comment écrire une structure MVC complètement immuable. Ensuite, je vous montrerai des contrôleurs réutilisables et un système de routage.

Outre l'utilisation de nouvelles fonctionnalités de PHP telles que l'indication du type de retour (ndlr, PHP 7.0), les deux principaux changements que j'ai apportés à mon style de codage sont l'immutabilité et le fait de privilégier les arguments par rapport aux constructeurs ayant des propriétés connexes.

57.2. Hello World

Voici une démonstration du trinome MVC en immutable à travers le fichier mvc.php dont voici le contenu :

class Model
{
    public $text;
    
    public function __construct() 
    {
        $this->text = 'Hello world!';
    }
}

class View
{
    private $model;

    public function __construct(Model $model) 
    {
        $this->model = $model;
    }

    public function output()
    {
        return '<a href="mvc.php?action=textclicked">' . $this->model->text . '</a>';
    }
}

class Controller
{
    private $model;

    public function __construct(Model $model)
    {
        $this->model = $model;
    }

    public function textClicked()
    {
        $this->model->text = 'Text Updated';
    }
}

$model = new Model();

// It is important that the controller and the view share the model.
$controller = new Controller($model);

$view = new View($model);

if (isset($_GET['action'])) $controller->{$_GET['action']}();
echo $view->output();


57.3. Le modèle

Dans cet exemple, le modèle est mutable mais les autres composants ne le sont pas. Tout d'abord, rendons le modèle immuable :

class Model
{
    private $text;

    public function __construct($text = 'Hello World')
    {
        $this->text = $text;
    }

    public function getText()
    {
        return $this->text;
    }
    
    public function setText($text)
    {
        return new Model($text);
    }
}

Il n'est plus possible de modifier l'état du modèle une fois qu'il a été instancié. L'appel de la méthode setText crée une nouvelle instance. Tout endroit où l'instance originale est référencée ne verra pas de changement dans l'état du modèle. Le contrôleur et la vue devront être mis à jour pour utiliser cette nouvelle classe de modèle.

Auparavant, le modèle et le contrôleur partageaient une instance de modèle. Le contrôleur modifiait l'état du modèle et la vue lisait l'état de l'instance de modèle. Maintenant que le modèle est immuable, nous ne pouvons plus compter sur le fait que la vue et le contrôleur partagent la même instance de modèle. Au lieu de cela, l'action du contrôleur retournera une instance de modèle qui sera ensuite transmise à la vue.

57.4. Le contrôleur

Au lieu de mettre à jour l'état d'une instance de modèle existante et mutable, le contrôleur renvoie une nouvelle instance de modèle après avoir effectué ses modifications :

class Controller
{
    public function textClicked(Model $model): Model
    {
        return $model->setText('Text Clicked');
    }
}


Plutôt que de m'appuyer sur les constructeurs et les propriétés, j'ai choisi d'utiliser des arguments pour faire passer le modèle qui va être mis à jour. L'action du contrôleur reçoit maintenant une instance de modèle immuable et renvoie une nouvelle instance avec les modifications nécessaires.

Il est à noter que cela ne fait pas partie de MVC, l'utilisation d'arguments de constructeur avec des propriétés liées ou d'arguments à l'action de contrôleur ont le même effet. Mais en évitant les constructeurs, il n'est plus nécessaire de conserver le modèle comme une propriété et le contrôleur est considérablement simplifié. Moins de code pour faire le même travail est toujours une bonne chose.

57.5. La vue

La vue nécessite peu de modification pour arriver au même résultat (plus besoin de l'attribut publique).

class View
{
    public function output(Model $model)
    {
        return '<a href="mvc.php?action=textClicked">' . $model->getText() . '</a>';
    }
}

L'avantage de faire passer le modèle en argument est que une seule instance de View permet d'afficher autant de modèles que l'on souhaite.

57.6. Mise en place du trinome

Il ne reste plus qu'à instancier tout ce petit monde, plus besoin de passer le modèle aux constructeurs de la View et du Controller.

<?php
$model = new Model();
$controller = new Controller();
$view = new View();

if (isset($_GET['action']))
{
    $model = $controller->{$_GET['action']}($model);
}

echo $view->output($model);


Dans cette version immuable, le modèle est passé au contrôleur qui construit une nouvelle instance du modèle qui est ensuite passé à la vue.

57.7. Conclusion

Cette implémentation immuable de MVC présente certains avantages par rapport à la version mutable :

L'état est mieux géré de sorte que l'application ne souffre pas d'une action à distance où le changement d'un objet à un endroit (dans le contrôleur) provoque ensuite des changements dans un composant apparemment sans rapport (la vue).
Il y a moins d'État dans l'ensemble. Il n'y a plus de références aux différents objets en plusieurs endroits. Le contrôleur et la vue n'ont plus de référence au modèle, on leur donne une instance avec laquelle travailler au moment où ils en ont besoin, pas avant.

-- Fin de la traduction --

Bien évidemment, ce code ne doit pas se retrouver de cette façon dans votre application car le paramètre HTTP action doit être vérifié (ie : on contrôle que l'utilisateur a le droit ou non de faire l'action).

57.8. Vue d'artiste

Attention : le schéma suivant ne représente pas vraiment la beauté conceptuelle derrière le MVC.

58. Architecture des fichiers du projet

58.1. Architectures des dossiers en Symfony 4/5

Voilà un topic sur le sujet :
https://stackoverflow.com/questions/47594542/symfony-4-how-to-organize-folder-structure-namely-your-business-logic

Deux solutions sortent du lot avec des arguments différents.

58.1.1 Solution 1

-src
- Controller
    - Core
    - Todos
    - ...
- Entity
    - Core
    - Todos
    - ...
- Repository
    - Core
    - Todos
- Validator (or other Symfony oriented components)
    - Core
    - Todos
- Others (depend on project)
    - Core
    - Todos


L'avantage d'une telle arborescence est de retrouver toutes les implémentations d'une certaine catégorie dans un dossier principal.
Le problème est par contre le suivant : si on souhaite supprimer une fonctionnalité, il faut supprimer dans chaque dossier les fichiers qui concernent la fonctionnalité, on ne distingue pas un découpage simple des différents domaines de l'application.

58.1.2 Solution 2

├─ assets/
├─ bin/
│ └─ console
├─ config/
│ ├─ doctrine/
│ │ ├─ core/
│ │ └─ sample/
│ ├─ packages/
│ ├─ routes/
│ └─ validator/
│ │ ├─ core/
│ │ └─ sample/
├─ public/
│ └─ index.php
├─ src/
│ ├─ Core/
│ │ ├─ Controller/
│ │ ├─ Entity/
│ │ ├─ Repository/
│ │ └─ ...
│ ├─ Sample/
│ └─ ...
├─ templates/
│ ├─ core/
│ └─ sample/
├─ tests/
├─ translations/
├─ var/
│ ├─ cache/
│ ├─ log/
│ └─ ...
└─ vendor/

L'avantage d'une telle solution est qu'il est plus simple de retrouver tous les fichiers qui concernent une fonctionnalité. Par contre la configuration peut être plus compliquée ; dans le cas de doctrine, il ne suffit plus de dire que toutes les classes d'entités sont dans src/Entity/*(/*), mais plutôt dans src/*/Entity/*(/*).

🧙‍♂️️Je préfère cette solution-ci qui permet d'avoir un lien simple entre une url /domain et un dossier src/Domain.


58.2. Projet monolithique

Attention : ici, je distingue un projet monolithique et une application monolithique.
Le projet est pour moi l'ensemble des fichiers que les développeurs manipulent pour construire toutes les applications.
Chaque application ne doit être déployée qu'avec son code source.

Qu'est ce qu'il est plus simple : gérer dans son équipe un projet par application ou un repository git (qui peut être un composite de plusieurs .git) pour toutes les applications ?
La réponse semble évidente, et pourtant on a tendance à se dire que chaque application doit avoir son propre repository.

Alors, éventuellement, il est possible de configurer son IDE pour intégrer plusieurs repository .git

Voilà à quoi pourrait ressembler un projet contenant plusieurs applications :

application_[n]
    /src
    /config
        /routes
    /templates
    /var
        /cache
        /log
    /vendor
    composer.json
core
    /config
    /domains
        /Common
            /Infrastructure
                /Doctrine
                /RabbitMq
                /Elasticsearch
            /Service
            /Utility
        /Domain_[n]
            /Enumeration
            /Helper
            /Infrastructure
            /Model
            /Service
            /Utility
    /vendor
    /composer.json


A noter que,
* l'utilisation du framework est liée à une application, cette application peut être native PHP ou non ;
* tout ce qui se trouve dans le dossier 'core' est frameworkless, c'est à dire que le code ne devrait pas dépendre d'un framework ou d'un composant tier (vendor) ; ce qui est potentiellement difficile à respecter.

Ce qui veut dire qu'il est possible, sous réserve évidemment que le framework/CMS tourne sous la même version de PHP que le code, de travailler sur plusieurs applications en gardant un espace propre à son métier (dossier core).

Il est possible d'avoir un .git au niveau de chaque application et d'avoir un gestionnaire de version de cette forme :
-company/application_1
-company/application_2
-company/application_3
-company/core

Bien sûr, si vos applications mélangent plusieurs technologies (ex: PHP/NodeJs/J2E), il faudra peut-être éventuellement avoir autant d'espaces de travail distincts.

59. Créer une extension PHP

💣️N'étant pas du tout un expert sur le sujet, tout ce qui suit est à prendre avec des pincettes.

Voici un petit tutoriel pour créer une extension PHP (sous Debian).

59.1. Les ressources

Le site officiel de Zend(https://www.zend.com/resources/writing-php-extensions)
La page Github d'un squelette d'extension PHP(https://github.com/improved-php-library/skeleton-php-ext)


59.2. Exemple : création de la fonction PHP triple

Voici un exemple d'implémentation d'une nouvelle fonction, appelée `triple` qui retourne un entier multiplié par trois.

Ce code est à placer dans le fichier skeleton.c du projet skeleton-php-ext.

ZEND_BEGIN_ARG_INFO_EX(arginfo_triple, 0, 1, 0)
    ZEND_ARG_TYPE_INFO(0, str, IS_LONG, 1)
ZEND_END_ARG_INFO()

static const zend_function_entry functions[] = {
    PHP_FE(triple, arginfo_triple)
    PHP_FE_END
};

PHP_FUNCTION(triple)
{
    zend_long integer;

    ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1)
        Z_PARAM_LONG(integer)
    ZEND_PARSE_PARAMETERS_END();

    RETVAL_LONG(integer*3);
}


Il faut également modifier le fichier php_skeleton.h pour ajouter le nom de la nouvelle fonction :

static PHP_FUNCTION(triple);


59.3. Exemple : création de la fonction PHP addition

Cela se complique un peu car nous allons créer maintenant une fonction avec deux arguments.

static const zend_function_entry functions[] = {
    PHP_FE(addition, arginfo_addition)
    PHP_FE_END
};

ZEND_BEGIN_ARG_INFO_EX(arginfo_addition, 0, 1, 0)
    ZEND_ARG_TYPE_INFO(0, a, IS_LONG, 1)
    ZEND_ARG_TYPE_INFO(0, b, IS_LONG, 1)
ZEND_END_ARG_INFO()

PHP_FUNCTION(addition)
{
    zend_long a;
    zend_long b;

    ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 2)
        Z_PARAM_LONG(a)
        Z_PARAM_LONG(b)
    ZEND_PARSE_PARAMETERS_END();

    RETVAL_LONG(a+b);
}


Il faut également modifier le fichier php_skeleton.h pour ajouter le nom de la nouvelle fonction :

static PHP_FUNCTION(addition);


59.4. Exemple : création de la fonction PHP guessThenumber

La règle : l'utilisateur indique un nombre, si le nombre est plus grand que le nombre à deviner, la fonction retourne -1, si le nombre est le même, la fonction retourne 0, et si le nombre est plus petit, la fonction retourne 1.

📖️️L'idée de ce petit jeu provient de ce TP en C sur le site d'OpenClassRooms(https://openclassrooms.com/fr/courses/19980-apprenez-a-programmer-en-c/14828-tp-plus-ou-moins-votre-premier-jeu).


Pour cet exemple, on peut utiliser une variable globale qui sera initialisée qu'une seule fois par la fonction guessTheNumber.

ZEND_BEGIN_ARG_INFO_EX(arginfo_guessTheNumber, 0, 1, 0)
ZEND_END_ARG_INFO()

static const zend_function_entry functions[] = {
    PHP_FE(guessTheNumber, arginfo_guessTheNumber)
    PHP_FE_END
};

zend_long THE_NUMBER_TO_FIND;
PHP_FUNCTION(guessTheNumber)
{
    zend_long guess_long;

    if (!THE_NUMBER_TO_FIND) {
        THE_NUMBER_TO_FIND = rand() % 101;
    }

    ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1)
        Z_PARAM_LONG(guess_long)
    ZEND_PARSE_PARAMETERS_END();

    zend_long guessed_status;
    if (THE_NUMBER_TO_FIND > guess_long) {
        guessed_status = 1;
    } else if (THE_NUMBER_TO_FIND < guess_long) {
        guessed_status = -1;
    } else {
        guessed_status = 0;
    }

    RETVAL_LONG(guessed_status);
}
#endif


Il faut également modifier le fichier php_skeleton.h pour ajouter le nom de la nouvelle fonction :

static PHP_FUNCTION(guessTheNumber);


En pratique voilà ce que ça donnerait :

# php -a
php > var_dump(guessTheNumber(40));
int(-1)
php > var_dump(guessTheNumber(30));
int(-1)
php > var_dump(guessTheNumber(28));
int(0)
php > var_dump(guessTheNumber(28));
int(0)


On peut éventuellement faire un reset du nombre à trouver dès qu'il est trouvé pour que l'amusement perdure indéfiniment.

59.4.0.1 Création de la fonction C resetTheNumber

zend_long THE_NUMBER_TO_FIND;

void resetTheNumber()
{
    THE_NUMBER_TO_FIND = rand() % 101;
}

PHP_FUNCTION(resetTheNumber)
{
    resetTheNumber();
}

PHP_FUNCTION(guessTheNumber)
{
    zend_long guess_long;

    if (!THE_NUMBER_TO_FIND) {
        resetTheNumber();
    }

    ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1)
        Z_PARAM_LONG(guess_long)
    ZEND_PARSE_PARAMETERS_END();

    zend_long guessed_status;
    if (THE_NUMBER_TO_FIND > guess_long) {
        guessed_status = 1;
    } else if (THE_NUMBER_TO_FIND < guess_long) {
        guessed_status = -1;
    } else {
        guessed_status = 0;
    }

    RETVAL_LONG(guessed_status);
}


59.5. Signatures des fonctions ZEND


Voici la signature de la fonction ZEND_PARSE_PARAMETERS_START_EX

define ZEND_PARSE_PARAMETERS_START_EX(flags, min_num_args, max_num_args)



60. Encodage


60.1. Les 3 petites règles pratiques

Voici 3 petites règles qui vont vous permettre de comprendre le problème d'affichage des caractères spéciaux et accentués.

Si dans votre page web vos accents ont la forme de "�", c'est que le texte est encodé en ISO et que le navigateur l'affiche en UTF-8.
Si dans votre page web vos accents ont la forme de "é", "î" ou "Ã", c'est que le texte est encodé en UTF-8 et que votre navigateur l'affiche en ISO.
Si dans votre page web vos accents ont la forme de "￾", c'est que généralement le texte est encodé en WINDOWS-1252 et que le navigateur l'affiche en UTF-8.

📖️️https://outils-javascript.aliasdmc.fr/generateur-caracteres-speciaux-accentues-html-css-js.php?#formConversion

61. Faire un projet de qualité sans framework

61.1. Contraintes

C'est possible à la condition de respecter strictement plusieurs choses :
* toutes les PSR doivent être appliquées à la lettre ;
* le langage est toujours à la dernière version ;
* les librairies sont toujours à jour (cela ne doit pas empêcher pas d'être prudent).

61.2. Démarrer un projet

Nous avons vu que la PSR-1 nous imposait si on faisait de la POO d'utiliser les namespaces.
Même en PHP 8.0, il ne semble pas y avoir d'autoloader par défaut qui puisse nous permettre d'utiliser les différents use.

61.2.1 Créer un autoload via spl_autoload_register

61.2.1.1 Créer le fichier autoload.php

On peut créer un fichier autoload.php et y écrire ceci :

<?php
spl_autoload_register(function ($className) {
$className = str_replace('\\', DIRECTORY_SEPARATOR, $className);
$filename = __DIR__ . DIRECTORY_SEPARATOR . $className . '.php';

    if (is_readable($filename)) {
        require_once($filename);
    }
});

61.2.1.2 Importer l'autoload dans l'index.php

Puis importer ce fichier dans un index.php de la façon suivante :

<?php
require_once('autoload.php');


Après quoi il sera possible dans le fichier index.php d'utiliser des use, eux-mêmes utilisant des use et des namespace.

61.2.1.3 Exemple d'arborescence

App\
    Service\
            MyService.php
index.php
autoload.php

Avec MyService.php :

<?php

namespace App\Service;

class MyService
{
    public static function myMethod(): string
    {
        return 'ok';
    }
}

et index.php :

<?php
require_once('autoload.php');

use App\Service\MyService;

echo MyService::myMethod();

Attention à bien respecter la PSR-4 : le respect de la casse est indispensable.

61.2.1.4 Défaut d'un autoload personnel

L'autoload doit être mis à jour dès qu'il y a des nouveautés en terme de PSR et de montée de version du langage.
Par exemple, en PHP 7.0 est introduit la possibilité de faire des use multiples, ce que ne prend pas en compte l'autoload du dessus.

61.2.2 Utiliser l'autoloader de Composer

Quoi de plus simple que d'utiliser l'autoloader certainement le plus populaire et le plus utilisé du moment ?

61.2.2.1 On commence par créer le composer.json à la racine de son projet

{
  "type": "project",
  "license": "MIT",
  "autoload": {
    "psr-4": {
      "App\\": "App/"
    }
  }
}


Puis on crée un projet via la commande suivante :

composer create-project

Cette commande va générer le dossier vendor et surtout le fichier autoload.php que l'on peut inclure dans son index.php
On peut alors utiliser les uses multiples.

<?php
require ('autoload.php');

use App\Service\{MyClass, MyOther};

MyClass::myMethod();
MyOther::myMethod();

61.2.2.2 Matcher le namespace App et le dossier src

Si vous souhaitez que toutes les classes définies dans le dossier src de votre projet correspondent au namespace App, il vous faut pour cela modifier la propriété autoload du composer.jon

{
  "type": "project",
  "license": "MIT",
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  }
}

Puis faire une mise à jour de l'autoload via :

composer update

Il est alors possible d'utiliser l'arborescence suivante :

src\
    Service\
            MyService.php
index.php
autoload.php

En gardant le namespace App.

Cela semble assez antinomique que la PS4 autorise ce type de mapping mais c'est une solution plus souple que de contraindre les namespaces aux noms de dossiers.

61.2.3 Que faire sans un moteur de template ?

Sur un projet qui n'aurait pas la chance d'avoir un moteur de template évolué (twig/smarty pour les plus connus), on peut éventuellement s'en sortir en faisait quelque chose comme la solution que je présente dans la page sur "les différentes façons de construire une page HTML".



62. Installer rapidement un serveur PHP en local avec nginx

Le couple nginx+php-fpm est une bonne alternative à apache2.

# Fichier de configuration nginx

62.1. Ajouter le nouveau domaine dans le fichier hosts

vim /etc/hosts

127.0.0.1 new_domain.local


62.2. Créer le fichier de configuration nginx

Voici un exemple du fichier de configuration nginx à créer pour le nouveau domaine :

vim /etc/nginx/sites-enabled/new_domain


server {
server_name new_domain.local;
root /var/www/html/new_domain;

location / {
# try to serve file directly, fallback to index.php
try_files $uri /index.php$is_args$args;
}

location ~ ^/(.*)\.php(/|$) {
fastcgi_pass unix:/var/run/php/php-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
}
error_log /var/log/nginx/new_domain_error.log;
access_log /var/log/nginx/new_domain_access.log;
}


62.3. Redémarrer nginx

On peut utiliser reload au lieu de restart :

sudo service nginx reload


62.3.1 Acceder au site :

Ouvrir le navigateur web et taper :
http://new_domain.local/

63. Les différentes façons de construire une page HTML.


Alors que le PHP est certainement le langage le plus populaire pour faire du web, la façon dont le contenu des pages est généré est, plus de 25 ans après sa création, encore très archaïque et disons-le franchement, pas très pro ! C'est pour dire : d'un côté on a les 23 design patterns écrits par 4 types il y a trois siècles dont les règles sont toujours aussi pertinentes, et de l'autre il y a l'espèce de chose visqueuse dont on se sert pour faire quelques balises HTML qui bave de tous les côtés. Cet article fait un peu le tour des façons les plus courantes de créer un contenu HTML à partir du PHP.

63.1. La méthode old school

Il est presque inutile de la présenter car tout développeur l'a rencontrée. Je veux bien sûr parler de cette façon-ci de faire : on dispose d'un fichier PHP et on passe notre temps à ouvrir et à fermer les balises <?php?> pour y intégrer notre code HTML.

De cette façon exactement :

<?php
$myArray = ['chaussure', 'chocolat'];
foreach ($myArray as $item) {
?>
    <li><?php echo $item;?></li>
<?php
}

Il va sans dire que si l'on commence en plus à y intégrer du code JS, cela devient vite un cauchemar à gérer (même si c'est à première vue malin de créer du code JS dynamique).

L'indentation est difficilement respectable (on mélange forcément l'indentation du PHP et l'indentation du HTML) ;
minimiser le contenu de la page HTML est compliqué : il faudrait récupérer ce que génère le PHP et le convertir, ce qui demande de configurer son serveur web pour le faire via des modules spécifiques alors que cela pourrait être fait en PHP directement ;
l'injection de code malveillant est plus probable avec ce genre de code car si l'on commence à injecter des valeurs provenant des utilisateurs (ex: la liste de vos plats préférés, plat que l'utilisateur peut librement écrire, TODO: écrire un article sur l'injection XSS), celui-ci pourra modifier le DOM de notre page sans trop de difficulté. Il faudrait éventuellement utiliser des fonctions de sécurité un peu partout, ce qui serait assez chronophage, inesthétique et source d'oubli ;
la duplication de code est facilitée. Dès lors, quid de l'ajout d'attributs HTML sur nos éléments ? Faut-il revenir sur tous nos fichiers PHP en faisant une recherche globale de chaque élément ?


63.2. La méthode via echo et autres

Je ne saurais vraiment dire si c'est pire que la version oldschool, peut-être que l'avantage est de toujours pouvoir encapsuler la génération du HTML dans des fonctions.

On ferait comme ceci :

<?php
$myArray = ['chaussure', 'chocolat'];
foreach ($myArray as $item) {
    echo sprintf('<li>%s</li>', $item);
}

Ici c'est toujours très mauvais, cependant on peut évacuer plusieurs problèmes induits par la solution oldchool, notamment celle de l'injection HTML par l'utilisateur. Il est en effet possible d'adapter notre code de cette façon-ci :

<?php
$myArray = ['chaussure', 'chocolat'];
foreach ($myArray as $item) {
    echo makeHTMLLine($item);
}

function makeHTMLLine(string $item): string
{
    return sprintf('<li>%s</li>', htmlentities($item));
}


On peut imaginer construire tout un tas de fonctions pour créer nos éléments HTML qui appelleraient une fonction d’échappement (ici il s'agit de htmlentities, il vaut mieux ne l'écrire qu'une seule et unique fois dans tout son code pour potentiellement protéger l'ensemble du site).

Au passage, il ne serait pas élégant d'intégrer le echo dans la fonction, mieux vaut lui aussi ne l'écrire qu'une et unique fois dans son code et ne l'appeler qu'à la fin de l’exécution.

63.3. Empile moi du HTML

Troisième solution donc, qui est d'une certaine façon une amélioration de la seconde : on construit le contenu de son HTML que l'on stocke dans une variable $htmlContent puis on l'affiche à la fin de l’exécution du programme.
Ce n'est certes toujours pas l'idéal mais on peut éventuellement imaginer que cette variable contient dans son initialisation l'ensemble du squelette de la page HTML que l'on souhaite construire (ie. toutes les balises de type meta, style, jusqu'au body).

63.4. Mon premier template HTML

On peut même imaginer que ce squelette est dans un fichier squelette.html, chargé via une méthode de type file_get_contents puis modifié à coup de str_replace.

63.4.1 Exemple d'un tel fichier squelette.html

<html>
    <body>{{body}}</body>
</html>

63.4.2 On ferait ensuite ce genre de code PHP :

<?php 
$htmlContent = file_get_contents('squelette.html');
$htmlContent = str_replace('{{body}}', '<div>mon contenu</div>', $htmlContent);
echo $htmlContent;

63.4.3 Pour avoir ce résultat en sortie de l’exécution :

<html>
    <body><div>mon contenu</div></body>
</html>

Les symboles moustaches moustaches {{}} peuvent bien sûr être remplacés par n'importe quoi d'autre, sous réserve que ce ne soient pas des caractères fréquents dans les mots syntaxiques du HTML. Vous pouvez essayer "<<>>" mais ce ne serait pas forcément judicieux, rien que pour la lisibilité.
En outre, on peut tout à fait imaginer que certains morceaux de squelette soient enregistrés dans des fichiers secondaires afin d'être réutilisés plus tard, par exemple le contenu du pied de page, le menu, l'entête, ou même des blocs indépendants (carrousel d'images, boutons etc...).

Rien qu'avec ces quelques bouts de code, on peut dire qu'on a conçu un moteur de template ! Avec ici une règle assez simple : remplacer le pattern {{body}} par du contenu dynamique.

Mais, cela se complique fortement dès que l'on souhaite ajouter des conditions sur notre contenu, faire des boucles ou encore de l'héritage entre les pages. Ecrire un tel moteur s'avère alors plus difficile. Mais certains l'ont fait.

63.5. Système de template avec la force de PHP

L'implémentation d'un moteur qui ne ferait que de l'injection de valeurs trouve vite ses limites. Il n'est en effet non possible de personnaliser son HTML en fonction des variables PHP (comme faire des listes, afficher un message différent en fonction d'une condition etc...).
Pour pallier à ceci, on peut créer un fichier de template qui ressemblerait à un template HTML mais qui serait en fait une fonction PHP.

On peut éventuellement s'en sortir de cette façon-ci via un Templater maison :

class Templater
{
    public static function render($tplName, $parameters)
    {
        $tplFilePath = __DIR__.'/template/'.$tplName.'.php';
        if (!is_file($tplFilePath)) {
            throw new \Exception('Template '.$tplFilePath.' is not found.');
        }

        $rendering = require('template/'.$tplName.'.php');

        return $rendering($parameters);
    }
}


Et créer un fichier de template comme 'page.php' par exemple :

<?php $page = function($p) { ?>
    <html>
        <head></head>
        <body><?php echo $p['name'];?></body>
    </html>
<?php } ?>


On l'appelerait ensuite de cette façon-ci :

require_once('Templater.php');

echo Templater::render('page', array(
'name' => 'dmeloni',
));


Ce n'est pas ce qu'il y a de plus classe mais l'avantage reste tout de même d'utiliser la puissance de PHP dans un template (donc de pouvoir profiter des switch (ça n'existe toujours pas en twig), du caractère triple égal (===), du foreach et du for classique (plutôt qu'un loop.index pour savoir où on est dans le tableau) et des constances de classes MyClass::CONSTANCE_NAME plutôt que d'utiliser la chose un peu barbare qui est const('App\\Domain\\...\\MyClass::CONSTANCE_NAME')).
Par contre, on perd la grande force de l'héritage des templates...donc dès que notre application dépasse une seule page HTML, la solution full PHP deviendra vite limitante (sauf à réécrire soi-même un moteur de template plus évolué...) afin d'éviter le copier coller des balises HTML communes entre les pages.

63.6. Moteurs de templates populaires

Pour n'en citer que deux, Twig et Smarty.
Alors autant le dire franchement, c'est certainement mieux que toutes les autres solutions dont on vient de parler, mais cela reste d'assez mauvaises solutions.

Car avec ce genre de moteur, on en revient à écrire des choses qui finalement sont extrêmement similaires avec notre solution old school.

Pour preuve, comparez ce qui suit.

63.6.1 Solution old school

<?php

$myArray = ['chaussure', 'chocolat'];
foreach ($myArray as $item) {
?>
    <li><?php echo $item;?></li>
<?php
}


63.6.2 Solution new school

En Twig 2 et 3

{% foreach item in myArray %} 
    <li>{{item}}</li>
{% endfor %}


En Smarty 3 (je ne sais plus pour la 2, surement que cela marche aussi)

{foreach $myArray as $item}
    <li>{$item}</li>
{/foreach}

Autrement dit, on fait exactement la même erreur avec ces moteurs de templates qu'avec du PHP de base : on mélange la logique et le squelette de la page, et ça c'est mal car l'intégration du HTML n'en sera que plus compliqué puisque l'on va mélanger des conditions, des boucles et pire parfois des instances de classes PHP que l'on injecterait dans le template comme ci de rien n'était avec de l'affichage de listes, de tableaux, d'images etc..
Donc quid dès lors que l'on revient sur la logique (ie, les règles métiers) ?: Il faut revenir sur tous nos templates et prendre le risque de faire du HTML invalide ou même des templates qui vont faire planter la page puisque telle ou telle variable renverra un null pointer exception ou quoique cela puisse être du même style, sérieusement une exception lancée au moment de la génération de la page ?! Est-ce vraiment sérieux ?

Donc, dans l'idéal, il faut séparer le template du code métier. Cela permettra dans le cadre d'une entreprise par exemple de permettre à l'intégrateur HTML de faire le design de ses pages sans se poser de question sur comment sont faits les objets manipulés par l'application.

Pour faire un autre parallèle qui ne divise quasiment plus les développeurs web : on ne mélange pas non plus le CSS et le HTML.

63.6.3 Les moteurs de templates bientôt populaires

Pour faire bref : ce sont ceux qui permettent d'injecter des valeurs dans notre template via des pointeurs au niveau du DOM (ex: Xpath ou règles CSS)

Exemple, voici le squelette de la page :

<html>
    <ul><li></li</ul>
</html>

Et une proposition de syntaxe :
newPopularTemplateEngine->do('repeat item in items to the li element', $items, $templateContent);

Contrairement aux moteurs classiques où l'on injecte la plupart du temps un tableau de variables que l'on réutilise dans le contenu du template, ici le squelette de la page doit pouvoir être ouvert via le navigateur, avec un contenu Lorem Ipsum pourquoi pas puisque celui-ci serait effacé par le moteur au moment de l'injection du contenu.

Le TAL est un langage qui est une réponse à cette problématique, il est implémenté dans plusieurs langages de programmation. Son approche est par contre un peu austère car les pointeurs sont écrits en XML avec des tags propres.
Le Haml est également une solution via des pointeurs proches du CSS.
Transphporm est surement pour l'heure la solution la plus porteuse d'espoir, il est fort probable que son nom apparaisse un de ces jours dans la liste du Awesome PHP

A noter que le moteur nommé Plates fait pale figure comparé à ces deux-là car il est très proche de la philosophie derrière les JSP.


64. Les types

Les types les plus courants en PHP sont les suivants :
* bool
* float (aussi appelé double)
* int
* string
* array
* object (qui représente l'abstraction de toutes les instances de classe)

Il existe aussi le non type null.

64.1. Le null

Le null est par définition ce qui est différent du non null. Ce qui est particulier avec le null, c'est qu'il y a une ambiguité car une variable à laquelle on associe une valeur nulle a également un type null.
Dès lors, si on définit une valeur et donc le type d'une variable, le null est une valeur qui ne devrait plus être permise.

64.1.1 Eviter le null en signature de méthode ?

Idéalement, moins il y a de types différents en retour de méthode, et plus simple sera l'utilisation de celle-ci par les utilisateurs, car les cas seront moins nombreux à gérer : il vaudrait donc mieux éviter d'utiliser le null dans son code car cela peut poser pas mal de problèmes lorsque la valeur nulle d'une variable se répand à travers les différentes méthodes. En pratique, cela ne simplifie pas pour autant le code et donc tout le reste (maintenabilité, temps d'exécution...).

Plutôt que d'écrire ceci :

function getMyArray()
{
    if (/* condition pouvant valoir false */) {
        return ['key' => 'value'];
    }

    return null;
}


On pourrait écrire cela :

function getMyArray()
{
    $myArray = []; 
    if (/* condition pouvant valoir false */) {
        $myArray = ['key' => 'value'];
    }

    return $myArray;
}

Ici, on garantit à l'utilisateur de la méthode getMyArray que le retour sera toujours un tableau, il pourra alors utiliser des fonctions d'itérations sur le retour de getMyArray sans avoir besoin de faire une vérification du retour comme is_array.

Evidemment, au niveau de l'exécution, la valeur affectée à myArray sera un tableau, donc cela consommera de fait plus de mémoire vive, mais ce point est clairement négligeable comparé au reste de la plupart des projets.

Là où cela devient moins pertinent de garantir un seul type de sortie, c'est dans le cas par exemple où la méthode doit chercher une entité stockée en base de données, il serait peut-être plus pertinent d'utiliser une méthode supplémentaire pour compter par exemple le nombre de cette entité en base. Cette dernière méthode pourrait en outre stocker dans un cache les données de l'entité pour épargner une requête supplémentaire à la base.

Voici une proposition, admettons qu'on ait cette méthode :

(findMyRessource($id) : myRessource);


Si on code avec l'usage du null, on aura plutôt ce genre de signature ci :

(findMyResource($id) : myResource|null);


et donc l'utilisateur sera obligé de tester que le retour ne vaut pas null afin d'utiliser cette ressource (par exemple : afficher une représentation de celle-ci à l'écran).

Il pourra faire ça de cette façon-là :

// Bout de code d'un controlleur Symfony qui récupère les infos de la ressource avant de la passer au template géré par Twig.
return $this->render('xxx.twig.html', [
    'myResource' => findMyResource($id)
]);

{# Code Twig #}
{% if myResource is not null %}
    <span>{{myResource.content}}</span>
{% else %}
     <span>No content</span>
{% endif %}


Ici, il doit gérer le fait que la ressource puisse avoir deux types différents (le null et object (myInstance)).

Pour éviter l'usage du null, on pourrait créer une autre méthode afin que la méthode findMyResource n'ait qu'un seul type de retour :

(myResourceExists($id) : bool);
(findMyResource($id) : myResource);


La première méthode retournera true ou false selon l'existence de la ressource, et la seconde méthode retournera la ressource en elle-même. Cependant, cela oblige de mettre des gardes fous dans les deux méthodes afin de retourner des Exceptions dans le cas où quelque chose se passe mal (récupération dans le cache par exemple qui ne fonctionnerait plus).

Et donc on aurait deux façons d'utiliser cela, soit directement dans le template :

$myResourceExists = myResourceExists($id);
if ($myResourceExists) {
    $myResource = findMyRessource($id);
}
return $this->render('xxx.twig.html', [
    'id' => $id,
]);

{# Code Twig #}
{% if myResourceExists(id) #}
    <span>{{findMyRessource($id).content}}</span>
{% else #}
    <span>No content</span>
{% endif #}

Cela demande au passage de créer deux fonctions Twigs.

Soit dans le controlleur :

$myResource = null;
$myResourceExists = myResourceExists($id);
if ($myResourceExists) {
    $myResource = findMyRessource($id);
}

return $this->render('xxx.twig.html', [
    'myResource' => $myResource,
]);

{# Code Twig #}
{% if myResource is not null #}
    <span>{{myResource.content}}</span>
{% else #}
    <span>No content</span>
{% endif #}


On voit ici les limites de la logique derrière l'idée de ne pas utiliser le null : on augmente le nombre de lignes de code, ainsi que leur complexité, en augmentant les risques de plantage

64.1.2 Comparer deux null entre eux

Ce genre de code par ailleurs n'a pas vraiment de sens :

$variableA = null;
$variableB = null;
$variableA === $variableB; // ceci vaut le bool true.


Car un null ne représentant rien, il est impossible de comparer deux nulls.

Exemple : Un non nombre de pommes dans un non sac.

$appleNumbers = null;
$bag = null;
$bag === $appleNumbers;


Ici, du point de vue de l'interpréteur, la dernière ligne vaudra true, mais on comprend assez facilement que fonctionnellement parlant, cela n'a aucun sens.

Par contre, si on déclare une variable nommée $null qui sert d'étalon (pour un test unitaire par exemple), ça aurait éventuellement un sens (en PhpUnit, on utiliserait le AssertNull voire un AssertTrue(\is_null($var))).

65. Migrer une application de Symfony 3.4 à 5.4


Si votre application tourne sous Symfony 3.4 et que vous avez un peu de temps devant vous, je vous conseille de passer directement le code afin qu'il soit directement compatible avec Symfony 5.4 (actuellement la version stable de Symfony).

Voici quelques pièges à éviter pour que la migration soit la plus efficace possible :

65.1. Le fichier bundles.php

Le fichier bundles.php est le fichier où sont chargés tous les vendors spécifiques à Symfony (que l'on nomme toujours Bundle).

L'ordre des bundles définie la priorité de chargement.
C'est à dire que si l'un des vendors a une dépendance avec un autre, il est important de vérifier que le vendor nécessaire est chargé avant.

Ainsi, je vous conseille de commencer par toujours charger les composants Symfony avant les composants tiers.

return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
    Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
    Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
    Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
    Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],

    My\Extra\MyExtraBundle\MyExtraBundle::class => ['all' => true],
];

Ici, le bundle MyExtraBundle est chargé après ses éventuelles dépendances.

Pour connaitre la liste des dépendances d'un Bundle, il suffit de regarder le composer.json du Bundle tier.

65.2. Les fichiers de config .yaml

Contrairement à Symfony 3.4 où la configuration se trouve dans le dossier app/config, en SF5.4, tout se trouve dans le dossier config.

Si vous avez besoin que les deux versions de l'application co-existent pendant un temps (ie. conserver les fichiers de configuration dans le dossier app/config), il est tout à fait possible d'importer les .yml de la version 3.4 dans les .yaml de la version 5.4.

Egalement, il faut faire attention à l'ordre de priorité des fichiers de configuration.

Par exemple, le fichier package/dev/special_config.yaml sera moins prioritaire que le fichier package/special_config.yaml et sera ignoré même si vous chargez le projet en mode dev.
Il faut alors :
- soit supprimer le fichier package/special_config.yaml et le recréer pour chaque environnement package/{environnement}/special_config.yaml
- soit supprimer le fichier package/dev/special_config.yaml et créer le fichier package/special_config_dev.yaml

65.3. Remplacement d'assetic par Webpack Encore

Pour installer Webpack Encore, il suffit de lire la doc officielle (https://symfony.com/doc/current/frontend/encore/installation.html).

65.3.1 Piège : les fonctions de vos .js ne seront plus globales par défaut

Le gros piège avec cette migration est que le code de vos fichiers .js ne sera plus global. C'est à dire que si vous utilisez certaines fonctions directement dans votre html (soit avec la balise <script> soit dans les attributs HTML), elles ne pourront pas fonctionner si elles ne sont pas déclarées globlales.

Pour déclarer une fonction globale, il y a au moins trois façons de faire :

- soit vous utilisez le mot clef "global." qui est convertie par Webpack en fonction du contexte pour que la fonction soit disponible ;
- soit vous utilisez le mot clef "window." qui ne nécessite pas d'être convertie par Webpack et qui fonctionnera également dans la version Assetic du projet ;
- soit vous déclarez 'inline' le script directement dans le fichier de template twig.

65.3.1.1 Version compatible application sous Assetic ET application sous Webpack

Avant :
function toto(arg) {
    // ...
}

Après : 
window.toto = function(arg) {
    // ...
}


65.3.1.2 Version compatible uniquement via Webpack

Avant :
function toto(arg) {
    // ...
}

Après :
global.toto = function(arg) {
    // ...
}


65.3.2 Piège : la transpilation sass interdit les extends dans les balises @media

Note : ce qui suit est à prendre avec des pincettes.

Alors qu'avec Assetic (et son transpileur sass), il est possible de faire des extends à l'intérieur de tag @media. Via webpack, cela n'est plus possible, il faut alors :
- soit sortir le extends du @media et faire un extends d'un pointeur déjà lié à un @media

@extends .justify-content-sm-center;


- soit mettre l'équivalence css directement dans le tag @media (eg: "justify-content:center;")

@media

  justify-content:center;
}


65.3.3 Piège : jquery ou $ ne fonctionne plus

Egalement, en chargeant jquery via Webpack, l'alias $ ne sera plus disponible pour les autres scripts.
Il faut indiquer à Webpack de rendre disponible jQuery de façon globale.

Fichier app.js
import "jquery";
window.$ = window.JQuery = jQuery;


Note : il est possible aussi d'utiliser le mot clef "global" à la place de "window".

65.3.4 Piège : priorité des librairies

Egalement, l'ordre dans lequel sont déclarés les fichiers .js dans le app.js est important, il faut commencer par les librairies fondamentales.
(ex: jquery > jqueryui > bootstrap > bootstrap-plugin1... > librairie-js-quelconque)

65.4. Chargement des templates

Sous SF5.4, les fichiers de templates twig doivent normalement se trouver dans les dossiers "templates".
Il est possible de conserver les dossiers "Resources" le temps de la bascule de la nouvelle version afin de simplifier la maintenance entre les deux versions de l'application.

Pour déclarer le dossier Resources/views comme un dossier de templates, il suffit de modifier la configuration de twig de cette façon :

# config/packages/twig.yaml
twig:
paths:
    'OldBundle/Resources/views': 'Old'


Les fichiers de templates seront alors toujours accessibles via "@Old".

65.4.1 Piège : l'utilisation du double points (ou colon) ':' pour charger les templates

A priori (je n'ai pas trouvé d'historique sur le sujet), il n'est plus possible d'utiliser le double-points pour indiquer le chemin d'un template, il est possible d'utiliser le slash, qui marchera quelque soit la version de Symfony :

'@Old/Domain:index.html.twig' -> ne fonctionne plus sous Symfony 5.4
'@Old/Domain/index.html.twig' -> fonctionnera sous Symfony 3.4 et Symfony 5.4


Cela vaut pour tous les chargements :

Dans les fichiers *.twig : 
{% use ''
{% extends ''
{% embed ''
{% form_theme form ''

Dans les fichiers *.php :
->render('')


65.5. L'usage de block twig non déclarés

Si le block n'existe pas, cela déclenchera une erreur.

{# Erreur lors du render #}
{{ block('block_inexistant') }} 


65.6. Le routing des Controllers

65.6.1 Dans les fichiers routing.yml

Il vaut mieux oublier la syntaxe avec les deux-points dans les fichiers de type routing.yml :

Avant : 
nom_route: 
    path: /
    defaults : {_controller : "AppBundle:Domain:index"}

Après :
nom_route:
    path: /
    defaults : {_controller : AppBundle\Controller\DomainController::indexAction}


65.6.2 Dans les fichiers de templates .twig

Même chose dans les templates, il vaut mieux éviter d'utiliser les deux points comme séparateur.

Avant :
{{ render(controller('AppBundle:Domain:index')) }}

Après :
{{ render(controller('AppBundle\\Controller\\DomainController:indexAction')) }}


65.7. L'utilisation du ->get dans les Controller

En Symfony 5.4, le ->get('nom_service') devient obsolète.
Il est toujours utilisable pour les services classiques de Symfony (router / twig / session) mais ne l'est plus pour les services personnalisés de votre projet.

Il devient alors nécessaire de déclarer les services dans les constructeurs de vos controlleurs, de les charger via l'autowiring (ou sans en déclarant manuellement les noms des services dans les fichiers services.yaml/xml).

Avant : 
public function indexAction()
{
    $this->get('mon_service')->do();
    // ...
}

Après (ici le code en PHP 7.4): 
private MonService $monService;

public function __construct(MonService $monService)
{
    $this->monService = $monService;
}

public function indexAction(): Response
{
    $this->monService->do();
    // ...
}


Note : le suffixe "Action" est conservé toujours dans l'optique où l'ancienne version du code sous SF3.4 est toujours en fonctionnement.

65.8. L'utilisation du getParameter

De la même façon, il n'est plus possible de récupérer un paramètre de configuration via la méthode getParameter du Controller.

Il y a alors au moins deux façons de faire :
- soit injecter dans le construct le service ParameterBag :

private ParameterBagInterface $monService;

public function __construct(ParameterBagInterface $parameterBag)
{
    $this->parameterBag = $parameterBag;
}

public function indexAction(): Response
{
    $this->parameterBag->get('nom_parametre'); // Valeur du paramètre
    // ...
}


💡️On peut même créer une méthode protected function getParameter(string $name) qui retournerait $this->parameterBag->get('nom_parametre')


- soit injecter directement le paramètre dans le construct et utiliser l'autoconfigure dans le services.yaml (ou le déclarer manuellement également).

private string $parameter;

public function __construct(string $parameter)
{
    $this->parameter = $parameter;
}

public function indexAction(): Response
{
    $this->parameter; // Valeur du paramètre
    // ...
}



65.9. Piège : utiliser des choses supprimées

65.9.0.1 $form->isValid()

Il n'est plus possible d'utiliser isValid() directement sans vérifier au préalable que le formulaire est soumis.
L'utilisation de la méthode isValid doit toujours être précédée par la méthode isSubmitted.

Il faut donc faire ceci :

Avant : 
$form->isValid();

Après :
$form->isSubmitted() && $form->isValid();


65.9.0.2 choices_as_values

Le paramètre choices_as_values dans les FormType n'existe plus. Il faut le supprimer en veillant à tester que les formulaires fonctionnent toujours.

65.9.0.3 new Email(['strict' => true])

Le validator Email (Symfony/Component/Validator/Constraints/Email) a changé, il ne faut plus utiliser le paramètre 'strict', il faut le remplacer par le paramètre 'mode'.

Avant :
new Email(['strict' => true]);

Après :
new Email(['mode' => Email::VALIDATION_MODE_STRICT]);


65.9.0.4 transchoice

La méthode 'transchoice' n'existe plus, il faut toujours passer par la méthode 'trans' et utiliser le paramètre %count% pour indiquer une quantité.

📖️️Documentation officielle : https://symfony.com/doc/current/translation.html#message-format


65.9.0.5 $eventDispatcher->dispatch('nom.event', $event)

La méthode dispatch ne prend plus qu'un seul argument, à savoir l'instance de la classe Event :

Avant :
$eventDispatcher->dispatch('nom.event', $event)

Après :
$eventDispatcher->dispatch($event)


65.9.0.6 Balises twig javascripts

Evidemment, vu qu'Assetic est aux oubliettes, les tags twig {% javascripts '...' %}{% endjavascripts %} et {% stylesheets '...' %}{% endstylesheets %} ne marcheront plus.
Ils sont remplacés par leurs homologues {{ encore_entry_script_tags('...') }} et {{ encore_entry_link_tags('...') }}

65.9.0.7 Injecter un paramètre sans l'autoconfigure et sans le Configuration.php

Il est possible d'injecter un paramètre en argument d'un service en le déclarant dans le services.yaml :

Avant (en passant par la méthode getConfigTreeBuilder de la classe DependencyInjection/Configuration.php du AppBundle):
nom_service: 
    class: App\MyClass
    arguments:
        - "%key.index%"

Après (en injectant directement le paramètre dans le service):
nom_service:
    class: App\MyClass
    arguments: 
        - "@parameter('key')['index']"

66. Préparer un entretien technique

Si vous avez la chance de passer un entretien technique avec moi, voici éventuellement les questions que je pourrais vous poser.
Bien sur, cela dépend de l'offre pourvue et du niveau d'expérience demandé par celle-ci.
Mais certaines questions seront les mêmes que vous soyez étudiant ou expérimenté.

66.1. Questions de cultures générales

66.1.1 Questions autour du web

* Comment fonctionne Internet ?
* Qu'est-ce qu'une application dite serveur web ?
* Existe-t-il d'autres types de serveur ?
* Comment communique concrètement un ordinateur-client avec un serveur-client ?

66.1.2 Questions autour de PHP

* Pouvez-vous m'écrire une classe qui hérite d'une classe abstraite qui implémente une interface ?
* Pouvez-vous me dire les nouveautés entre chaque nouvelle version de PHP ?
* Quelles sont les techniques courantes pour qu'une page web s'affiche très rapidement ?
* Combien connaissez vous de design pattern ? Quels sont ceux que vous avez déjà utiliser ?
* Que pensez vous de la dernière RFC en reflexion/validée/refusée ?
* Comment fonctionne un ORM ?
* Pouvez-vous m'écrire un algorithme qui filtre un tableau avec et sans regex ?
* Pouvez-vous m'écrire un algorithme qui ferait du multithread (le traitement en lui même ne sera qu'un sleep(rand(0, 20))) ?
* Quelles sont les différentes assertions sous PHPUnit ?

66.1.2.1 Questions autour du framework

Si vous postulez pour une mission sur un framework particulier :
* Quels sont les composants/sous namespaces les plus courants ?
* Qu'apporte le nouveau composant/bundle natif versus le composant/bundle apporté par quelqu'un d'autre ?
* Quels sont les différences notables entre chaque version majeure ?
* Où sont situés les fichiers de configuration sur la dernière version du framework ? Et la première ?

66.1.3 Questions autour du JS

* Pouvez-vous m'écrire un algorithme qui ferait un POST sans Jquery ?
* Quelles sont les solutions pour générer des bouts de JS dont une partie du contenu provient d'une base de données ?
* Comment vous controlez qu'une valeur existe dans un tableau ?
* Comment vous testez du code JS ?
* Quelle architecture employez-vous pour classer vos fichiers JS ?

66.1.4 Questions autour de HTML

* Pouvez-vous écrire le squelette classique d'une page en HTML 5 ?
* Savez-vous définir un tableau HTML ?
* A quoi servent les balises meta ?

66.1.5 Questions autour de CSS

* Pouvez-vous me définir une animation dont le texte subira une rotation à l'endroit puis à l'envers pendant 5 secondes ?

66.1.6 Questions autour de SCSS/LESS

* Pouvez-vous me dire comment définit-on un héritage entre les feuilles de style ?
* Pouvez-vous me faire varier une couleur sur un texte de façon aléatoire entre 1 et 5 secondes ?

66.2. Questions techniques

Et éventuellement, si après 1 heure d'entretien on a fait le tour des questions de culture G, on abordera en anglais des questions plus techniques :
* Théorie des algorithmes
* Structures Stack, Queue, LinkedList, HashMap, PriorityQueue, Binary Tree
* Algorithmes de tri (quicksort, merge sort, radix-sort, bucket sort…),
* Algorithme de recherche (binary search, fulltext search),
* Parcours des arbres (largeur/profondeur)
* Graphes (BFS, DFS …)
* Récursivité
* Gestion de la concurrence
* Manipulation de bits

66.3. Ne prenez pas peur !

Et enfin, pour vous rassurer : même si vous ne savez pas grand chose de tout cela, l'essentiel est de montrer sur peu de temps votre potentiel : n'hésitez donc pas à prendre les devants et imposer vos sujets techniques.
Après il est sûr que si vous postulez à un poste qui demande 5 ans d'expérience sur du JS et que vous n'avez jamais testé ni React, ni Angular, ni Vue entre 2017 et 2020, on pourra se poser quelques questions sur votre curiosité !

Idéalement, le code sera écrit au tableau blanc/noir.

67. Schématisation

67.1. Avant l'UML

67.1.1 La méthode OMT (Object Modeling Technique)

James Rumbaugh(https://fr.wikipedia.org/wiki/James_Rumbaugh), Michael Blaha, William Lorensen, Frederick Eddy et William Premerlani conçoivent cette méthode en 1991.

67.1.2 La méthode Booch

Grady Booch(https://fr.wikipedia.org/wiki/Grady_Booch) publie une première version de sa méthode, aussi appelée OOD (probablement "Object Oriented Design") en 1992.

67.1.3 La méthode OOSE (Object Oriented Software Engineering)

Ivar Jacobson(https://fr.wikipedia.org/wiki/Ivar_Jacobson) fonde cette méthode en 1992.

68. 1loc JS to nloc PHP

Il existe un site nommé 1loc.dev qui référence actuellement 275 snippets JS dont la spécificité est de tenir en une seule ligne.

Même si l'intéret est parfois assez limité, voici quelques-uns de ces snippets en PHP ; les différentes fonctions sont écrites sur plusieurs lignes pour rendre le code plus lisible.

68.1. Array

68.1.1 Cast a value as an array

function castArray($value) 
{
    return is_array($value) ? $value : [$value];
}
// Examples
castArray(1);
castArray([1, 2, 3]);


Notes : avec le typage fort, l'utilisation de ce genre de fonction ne devrait jamais avoir lieu.

68.1.2 Check if an array is empty

function isEmpty($arr)
{
    return empty($arr);
}
// Examples
isEmpty([]);            // true
isEmpty([1, 2, 3]);     // false

NB : bien que je suis pour le fait d'encapsuler certaines fonctions natives comme json_encode, htmlentities, locale etc... Ici, la question de la pertinence de le faire se pose un peu.
Lorsque l'on fait du typage fort, le count() suffit à tester si le nombre d'éléments du tableau est égal à 0. L'utilisation du empty est la source de beaucoup de bugs car empty('') fonctionne, empty([]) aussi, empty(0) aussi... et cela n'a pas vraiment de sens.

Je ne suis pas non plus d'accord avec la version JS qui ressemblerait à ceci en PHP :

function isEmpty($arr)
{
    return !is_array($arr) || count($arr) === 0;
}

Ici, la chaine "" n'est pas un tableau, et pourtant la méthode retourne true, alors que l'on souhaite savoir si le tableau est vide donc que la variable est à la fois un tableau et aussi que le nombre de ses élèments est supérieur à 0.
Donc la version que je préconise serait plutôt celle-ci :

function isEmpty($arr)
{
    return is_array($arr) && count($arr) === 0;
}

Ici, cette fonction vérifie à la fois le type de la variable et le fait que le tableau soit vide, c'est une version assez stricte et juste.

A noter que dans une version de PHP qui permet le typage fort, on écrirait directement ceci :

function isEmpty(array $arr)
{
     return count($arr) === 0;
}

Ce qui peut par contre potentiellement être un problème si le type de la variable n'est pas vérifié en amont (par exemple, c'est un enfant d'enfant d'un contenu json non maitrisé par un fournisseur).

68.1.3 Clone an array

Le terme 'clone' étant déjà un mot clef du langage PHP, je le renomme cloneArray pour cette proposition.

function cloneArray($array)
{
    return $array;
}
// Usage (lol)
$clonedArray = cloneArray([231]); // [231]

Alors oui, c'est assez inutile car par défaut on manipule des valeurs et non des références donc il suffit de copier la variable de façon classique $b = $a.

68.1.4 Compare two arrays

Cela sous entend la règle suivante : si tous les élements de deux tableaux sont identiques, alors les deux tableaux le sont.
Le code JS du site 1loc.dev propose deux solutions :
* la première est de convertir les deux tableaux en json et de comparer les chaines de caractères.
* la seconde est de vérifier d'une part si la taille des deux tableaux est la même puis de vérifier chaque élement.

En PHP, on peut directement utiliser le triple égal qui vérifie que l'ordre et le type de chaque élement du premier tableau correspond au second.

function isEqual($arrayA, $arrayB)
{
     return $arrayA === $arrayB;
}
// Examples
isEqual([1, 2, 3], [1, 2, 3]);      // true
isEqual([1, 2, 3], [1, '2', 3]);    // false

68.1.5 Compare two arrays regardless of order

Ici, il suffit dans un premier temps de trier les deux tableaux puis ensuite de les comparer.

function isEqual($arrayA, $arrayB)
{
    sort($arrayA);
    sort($arrayB);

    return $arrayA === $arrayB;
}
// Examples
isEqual([1, 2, 3], [1, 2, 3]);      // true
isEqual([1, 2, 3], [1, 3, 2]);      // true
isEqual([1, 2, 3], [1, '2', 3]);    // false

68.1.6 Convert an array of objects to a single object

Ici, c'est un peu particulier car la structure Object du JS est différente de celle du PHP ; en PHP, on continuerait de manipuler un tableau.
La fonction suivante ne gère pas les cas d'erreurs (notamment si la clef $key n'existe pas).

function toObject($arrayOfObjects, $key)
{
    $object = [];
    foreach ($arrayOfObjects as $subObject) {
        $object[$subObject[$key]] = $subObject;
    }

    return $object;
}

// Example
$result = toObject(
    [
        [ "id" => '1', "name" => 'Alpha', "gender" => 'Male' ],
        [ "id" => '2', "name" => 'Bravo', "gender" => 'Male' ],
        [ "id" => '3', "name" => 'Charlie', "gender" => 'Female' ],
    ],
    'id'
);

/*
[
    1 => ['id' => '0', 'name' => 'Alpha', 'gender' => 'Male',],
    2 => ['id' => '2','name' => 'Bravo','gender' => 'Male',],
    3 => ['id' => '3','name' => 'Charlie','gender' => 'Female',],
]
*/

68.1.7 Convert an array of strings to numbers

Ici, on peut utiliser la fonction array_map pour appliquer le cast float sur les différentes valeurs du tableau (à noter que la règle n'indique pas si on manipule que des valeurs entières).

function toNumbers($array) 
{
    return array_map(function($value) {
        return (float) $value;
    ], $array);
}

// Example
toNumbers(['2', '3', '4']);     // [2, 3, 4]

68.1.8 Count by the properties of an array of objects

Enfin une fonction un peu intéressante : compter le nombre d'élements en fonction de leur récurrence dans un tableau.

function countBy($array, $column)
{
    $uniqValues = [];

    foreach ($array as $subArray) {
        if (!isset($uniqValues[$subArray[$column]])) {
            $uniqValues[$subArray[$column]] = 0;
        }
        ++$uniqValues[$subArray[$column]];
    }

    return $uniqValues;
}

// Example
countBy([
    [ 'branch' =>  'audi',  'model' =>  'q8',  'year' =>  '2019' ],
    [ 'branch' =>  'audi',  'model' =>  'rs7',  'year' =>  '2020' ],
    [ 'branch' =>  'ford',  'model' =>  'mustang',  'year' =>  '2019' ],
    [ 'branch' =>  'ford',  'model' =>  'explorer',  'year' =>  '2020' ],
    [ 'branch' =>  'bmw',  'model' =>  'x7',  'year' =>  '2020' ],
], 'branch');

/*
array(3) {
    ["audi"] => int(2)
    ["ford"] => int(2)
    ["bmw"]  => int(1)
}
*/

68.1.9 Count the occurrences of a value in an array

Voici une première proposition :

function countOccurrences($array, $value)
{
    $occurences = 0;
    foreach ($array as $element) {
        $occurences += $element === $value ? 1 : 0; 
    }

    return $occurences;
}
// Examples
countOccurrences([2, 1, 3, 3, 2, 3], 2);                // 2
countOccurrences(['a', 'b', 'a', 'c', 'a', 'b'], 'a');  // 3

Voici une seconde proposition qui utilise la fonction PHP array_count_values.

function countOccurrences($array, $value)
{
    $arrayCountValues = array_count_values($array);
    $occurences = isset($arrayCountValues[$value]) ? $arrayCountValues[$value] : 0;

    return $occurences;
}
// Examples
countOccurrences([2, 1, 3, 3, 2, 3], 2);                // 2
countOccurrences(['a', 'b', 'a', 'c', 'a', 'b'], 'a');  // 3

68.1.10 Count the occurrences of array elements

Ici, la fonction array_count_values fait déjà le travail.

function countOccurrences($array) {
    return array_count_values($array);
}
// Examples
countOccurrences([2, 1, 3, 3, 2, 3]);               // [ 1 => 1, 2 =>  2,  3=> 3 ]
countOccurrences(['a', 'b', 'a', 'c', 'a', 'b']);   // [ 'a' => 3, 'b' => 2, 'c' => 1 ]

68.1.11 Create an array of cumulative sum

Voici une proposition, il existe peut-être une fonction plus courte.

function accumulate($array)
{
    $currentSum = 0;
    $cumulativeArray = [];
    foreach ($array as $element) {
        $currentSum += $element;
        $cumulativeArray[] = $currentSum; 
    }

    return $cumulativeArray;
}

// Example
accumulate([1, 2, 3, 4]);   // [1, 3, 6, 10]

68.1.12 Create an array of numbers in the given range

La fonction range existe déjà en PHP.

// Example
range(5, 10);   // [5, 6, 7, 8, 9, 10]

68.1.13 Create cartesian product

Je n'ai pas vraiment compris l'intéret de cette fonction mais voici son équivalence.

function cartesian($arrayA, $arrayB)
{
    $products = [];
    foreach ($arrayA as $elementA) {
        foreach ($arrayB as $elementB) {
            $products[] = [$elementA, $elementB];
        }
    }
    
    return $products;
}

// Example
cartesian([1, 2], [3, 4]);   // [ [1, 3], [1, 4], [2, 3], [2, 4] ]

68.1.14 Empty an array

Pas besoin de fonction pour ça.

$array = [];

68.1.15 Find the closest number from an array

Ici, il existe probablement beaucoup de solutions.
Pour cette proposition, on retourne le nombre dont l'écart avec le nombre soumis est le plus faible.
En triant le tableau, cela permet de sortir de la boucle dès lors que l'écart du nombre courant est plus grand que le précédent (on s'écarte de la valeur principale).
Il me semble nécessaire d'initialiser la valeur de l'écart minimal ($minDelta) à null afin que son scope soit clair pour le lecteur (sa première valeur réelle est celle du premier écart et non un entier arbitraire qui serait une source d'erreur).

function closest($array, $value)
{
    sort($array);
    $minDelta = null;
    $previousElement = null;
    foreach ($array as $element) {
        $currentDelta = abs($element - $value);
        if (is_null($minDelta)) {
            $minDelta = $currentDelta;
        } elseif ($currentDelta < $minDelta) {
            $minDelta = $currentDelta;
        } else {
            // The current delta is growing, so the closest number is reached.
            break;
        }
        $previousElement = $element;
    }

    return $previousElement;
}

// Example
closest([29, 87, 8, 78, 97, 20, 75, 33, 24, 17], 50);   // 33

68.1.16 Find the index of the last matching item of an array

function lastIndex($array, $predicateFunction)
{
    $reversedArray = array_reverse($array);
    foreach ($reversedArray as $index => $element) {
        if ($predicateFunction($element)) {
            return count($array) - $index - 1;
        }
    }

    return null;
}
// Example
lastIndex([1, 3, 5, 7, 9, 2, 4, 6, 8], function($i) {
    return $i % 2 === 1;
}); // 4

lastIndex([1, 3, 5, 7, 9, 8, 6, 4, 2], function($i) {
    return $i > 6;
}); // 5

68.1.17 Find the index of the maximum item of an array

function indexOfMax($array)
{
    return array_search(max($array), $array);
}
// Examples
indexOfMax([1, 3, 9, 7, 5]);        // 2
indexOfMax([1, 3, 7, 7, 5]);        // 2

68.1.18 Find the index of the minimum item of an array

function indexOfMin($array)
{
return array_search(min($array), $array);
}
// Examples
indexOfMin([6, 4, 8, 2, 10]);       // 3
indexOfMin([6, 4, 2, 2, 10]);       // 2

68.1.19 Find the length of the longest string in an array

function findLongest($array)
{
    $longestValue = 0;
    foreach($array as $element) {
        $longestValue = max($longestValue, strlen($element));
    }

    return $longestValue;
}

// Example
findLongest(['always','look','on','the','bright','side','of','life']);  // 6

68.1.20 Find the maximum item of an array

Le PHP a déjà une fonction max.

68.1.21 Find the maximum item of an array by given key

function maxBy($array, $column)
{
    $maxValue = null;
    $maxElement = null;
    foreach ($array as $key => $element)
    {
        $elementValue = $element[$column];
        if (is_null($maxValue) || $maxValue < $elementValue) {
            $maxValue = $elementValue;
            $maxElement = $element;
        }
    }
    
    return $maxElement;
}
// Example
$people = [
    [  'name' => 'Bar',  'age' => 24 ],
    [  'name' => 'Baz',  'age' => 32 ],
    [  'name' => 'Foo',  'age' => 42 ],
    [  'name' => 'Fuzz',  'age' => 36 ],
];
maxBy($people, 'age');   // {  'name' => 'Foo',  'age' => 42 }

68.1.22 Find the minimum item of an array

Le PHP a déjà une fonction min.

68.1.23 Find the minimum item of an array by given key

function minBy($array, $column)
{
    $minValue = null;
    $minElement = null;
    foreach ($array as $key => $element)
    {
        $elementValue = $element[$column];
        if (is_null($minValue) || $minValue > $elementValue) {
            $minValue = $elementValue;
            $minElement = $element;
        }
    }

    return $minElement;
}
// Example
$people = [
    [  'name' => 'Bar',  'age' => 24 ],
    [  'name' => 'Baz',  'age' => 32 ],
    [  'name' => 'Foo',  'age' => 42 ],
    [  'name' => 'Fuzz',  'age' => 36 ],
];
minBy($people, 'age');   // {  'name' => 'Bar',  'age' => 24 }

68.1.24 Flatten an array

Un peu de récursif dans cette histoire.

function flat($thing)
{
    $flattenArray = [];
    if (is_array($thing)) {
        foreach ($thing as $element) {
            $flattenArray = array_merge($flattenArray, flat($element));
        }
    } else {
        $flattenArray[] = $thing;
    }
    
    return $flattenArray;
}

// Example
flat(['cat', ['lion', 'tiger']]);   // ['cat', 'lion', 'tiger']

68.1.25 Get all arrays of consecutive elements

Le PHP a déjà une fonction nommée array_chunk.

68.1.26 Get all n-th items of an array

function getNthItems($array, $nth)
{
    $items = [];
    foreach ($array as $key => $element) {
        if ((($key+1) % $nth) === 0) {
            $items[] = $element;
        }
    }

    return $items;
}

// Examples
getNthItems([1, 2, 3, 4, 5, 6, 7, 8, 9], 2);    // [2, 4, 6, 8]
getNthItems([1, 2, 3, 4, 5, 6, 7, 8, 9], 3);    // [3, 6, 9]

68.1.27 Get all subsets of an array

A méditer...

68.1.28 Get indices of a value in an array

function indices($array, $value)
{
    $indices = [];
    foreach ($array as $key => $element) {
        if ($element === $value) {
            $indices[] = $key;
        }
    }
    
    return $indices;
}

// Examples
indices(['h', 'e', 'l', 'l', 'o'], 'l');    // [2, 3]
indices(['h', 'e', 'l', 'l', 'o'], 'w');    // []

68.1.29 Get the average of an array

function average($array)
{   
    if (empty($array)) {
        return 0;
    }

    return array_sum($array) / count($array); 
}

// Examples
average([1, 2, 3]); // 2
average([0, 10, 80]); // 30

68.1.30 Get the rank of an array of numbers

Bon, cette solution n'est surement pas la plus efficace du monde, mais elle fait le job.

function ranking($array)
{
    $ranking = [];
    $rsortArray = $array;
    rsort($rsortArray);
    foreach ($array as $value) {
        foreach ($rsortArray as $rank => $rSortValue) {
            if ($value === $rSortValue) {
                $ranking[] = $rank + 1;
                break;
            }
        }
    }
    
    return $ranking;
}

// Examples
ranking([80, 65, 90, 50]);      // [2, 3, 1, 4]
ranking([80, 80, 70, 50]);      // [1, 1, 3, 4]
ranking([80, 80, 80, 50]);      // [1, 1, 1, 4]

68.1.31 Get the sum of an array of numbers

Le PHP a déjà une fonction nommée array_sum.

68.1.32 Get the unique values of an array

Le PHP a déjà une fonction nommée array_unique.

68.1.33 Get union of arrays

Le PHP a déjà une fonction nommée array_merge.

68.1.34 Group an array of objects by a key

function groupBy($array, $column)
{
    $groupByArray = [];
    foreach ($array as $element) {
        $groupValue = $element[$column];
        if (!isset($groupByArray[$groupValue])) {
            $groupByArray[$groupValue] = [];
        }
        $groupByArray[$groupValue][] = $element;
    }

    return $groupByArray;
}
// Example
groupBy([
 [ 'branch' =>  'audi',  'model' =>  'q8',  'year' =>  '2019' ],
 [ 'branch' =>  'audi',  'model' =>  'rs7',  'year' =>  '2020' ],
 [ 'branch' =>  'ford',  'model' =>  'mustang',  'year' =>  '2019' ],
 [ 'branch' =>  'ford',  'model' =>  'explorer',  'year' =>  '2020' ],
 [ 'branch' =>  'bmw',  'model' =>  'x7',  'year' =>  '2020' ],
], 'branch');
/*
[
audi: [
        [ 'branch' =>  'audi', 'model' =>  'q8', 'year' =>  '2019' ],
        [ 'branch' =>  'audi', 'model' =>  'rs7', 'year' =>  '2020' ]
    ],
bmw: [
        [ 'branch' =>  'bmw', 'model' =>  'x7', 'year' =>  '2020' ]
    ],
ford: [
        [ 'branch' =>  'ford', 'model' =>  'mustang', 'year' =>  '2019' ],
        [ 'branch' =>  'ford', 'model' =>  'explorer', 'year' =>  '2020' ]
    ],
]
*/

68.1.35 Merge two arrays

Le PHP a déjà une fonction nommée array_merge.

68.1.36 Partition an array based on a condition

function partition($array, $partitionFunction)
{
    $partitionnedArray = [[],[]];
    foreach ($array as $element) {
        if ($partitionFunction($element)) {
            $partitionnedArray[1][] = $element;
        } else {
            $partitionnedArray[0][] = $element;
        }
    }

    return $partitionnedArray;
}
// Example
partition([1, 2, 3, 4, 5], function($n) {
    return $n % 2;
});// [[2, 4], [1, 3, 5]]

68.1.37 Remove duplicate values in an array

Cette fonction supprime les lettres qui sont en doublons (différent donc du array_unique).
function removeDuplicate($array)
{
    return array_keys(array_filter(array_count_values($array), function($value) {
        return $value === 1;
    }));
}

// Example
removeDuplicate(['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']); //  ['h', 'e', 'w', 'r', 'd']

68.1.38 Remove falsy values from array

Ceci équivaut à filtrer toutes les valeurs considérées commes nulle (0, null, false, '', []).

function removeFalsy($array)
{
    return array_filter($array, function($value) {
        return !empty($value);
    });
}

// Example
removeFalsy([0, 'a string', '', null, true, 5, 'other string', false]); // ['a string', true, 5, 'other string']

68.1.39 Shuffle an array

Le PHP a déjà une fonction nommée shuffle.

68.1.40 Sort an array of items by given key

Voici une proposition de solution, on commence par récupérer toutes les valeurs possibles, puis on les trie, et pour chacune des valeurs, on reconstitue un tableau final trié.

function sortBy($array, $column)
{
    $sortedValues = [];
    foreach ($array as $element) {
        $sortedValues[] = $element[$column];
    }
    sort($sortedValues);
    
    $sortByArray = [];
    foreach ($sortedValues as $value) {
        foreach ($array as $element) {
            if($value === $element[$column]) {
                $sortByArray[] = $element;
                break;
            }
        }
    }
    
    return $sortByArray;
}

// Example
$people = [
    [ 'name' =>  'Foo', 'age' =>  42 ],
    [ 'name' =>  'Bar', 'age' =>  24 ],
    [ 'name' =>  'Fuzz', 'age' =>  36 ],
    [ 'name' =>  'Baz', 'age' =>  32 ],
];
sortBy($people, 'age');
/*
[
    [ 'name' =>  'Bar', 'age' =>  24 ],
    [ 'name' =>  'Baz', 'age' =>  32 ],
    [ 'name' =>  'Fuzz', 'age' =>  36 ],
    [ 'name' =>  'Foo', 'age' =>  42 ],
]
*/

68.1.41 Sort an array of numbers

Le PHP a déjà une fonction nommée sort.

68.1.42 Split an array into chunks

Le PHP a déjà une fonction nommée array_chunk.

68.1.43 Swap the rows and columns of a matrix

On suppose ici que la matrice est bien "rectangulaire" (n x m).

function transpose($matrix)
{
    $transposedMatrix = [];
    for ($x = 0; $x < count($matrix) ; $x++) {
        for ($y = 0; $y < count($matrix[0]) ; $y++) {
            if (!isset($transposedMatrix[$x])) {
                $transposedMatrix[$x] = [];
            }
            $transposedMatrix[$x][$y] = $matrix[$y][$x];
        }
    }

    return $transposedMatrix;
}

// Example
transpose([       
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]);
/*
[
    [1, 4, 7],
    [2, 5, 8],
    [3, 6, 9],
]
*/

68.1.44 Swap two array items

function swapItems($array, $firstItemKey, $secondItemKey)
{
    $savedValue = $array[$firstItemKey];
    $array[$firstItemKey] = $array[$secondItemKey];
    $array[$secondItemKey] = $savedValue;

    return $array;
}

// Example
swapItems([1, 2, 3, 4, 5], 1, 4);   // [1, 5, 3, 4, 2]

68.1.45 Unzip an array of arrays

function unzip($arrayOfArrays)
{
    $unzippedArray = [];
    foreach ($arrayOfArrays as $subArray) {
        foreach ($subArray as $key => $value) {
            if(!isset($unzippedArray[$key])) {
                $unzippedArray[$key] = [];
            }
            $unzippedArray[$key][] = $value;
        }
    }

    return $unzippedArray;
}

// Example
unzip([['a', 1], ['b', 2], ['c', 3], ['d', 4], ['e', 5]]);  // [['a', 'b', 'c', 'd', 'e'], [1, 2, 3, 4, 5]]

68.1.46 Zip multiple arrays

function zip()
{
    $zippedArray = [];
    $arraysToZip = func_get_args();

    for ($i = 0; $i < count($arraysToZip[0]) ; $i++) {
        if (!isset($zippedArray[$i])) {
            $zippedArray[$i] = [];
        }
        foreach ($arraysToZip as $arrayToZip) {
            $zippedArray[$i][] = $arrayToZip[$i];
        }
    }

    return $zippedArray;
}

// Example
zip(['a', 'b', 'c', 'd', 'e'], [1, 2, 3, 4, 5]);   // [['a', 1], ['b', 2], ['c', 3], ['d', 4], ['e', 5]]

68.2. DateTime

68.2.1 Calculate the number of difference days between two dates

On suppose ici que les deux dates sont des instances de \DateTime.

function diffDays($date, $otherDate)
{
    $dateDiff = $date->diff($otherDate);

    return $dateDiff->days;
}

// Example
diffDays(new \DateTime('2014-12-19'), new \DateTime('2020-01-01')); // 1839

68.2.2 Calculate the number of months between two dates

On suppose également que les deux dates sont des instances de \DateTime.

function monthDiff($date, $otherDate)
{
    $dateDiff = $date->diff($otherDate);

    return (12 * $dateDiff->y) + $dateDiff->m;
}

// Example
monthDiff(new \DateTime('2014-12-19'), new \DateTime('2020-01-01')); // 153
monthDiff(new \DateTime('2020-01-01'), new \DateTime('2021-01-01')); // 12

68.2.3 Compare two dates

La fonction retourne true si la première date est supérieure à la seconde, false sinon.
Si on considère que les deux paramètres sont des \DateTime, la simple comparaison suffit.

function compare($a, $b)
{
    return $a > $b;
}

// Example
compare(new \DateTime('2020-03-30'), new \DateTime('2020-01-01'));    // true

68.2.4 Convert a date to YYYY-MM-DD format

Rien de plus simple avec une instance de DateTime, car format existe déjà.
function formatYmd($date)
{
    return $date->format('Y-m-d');
}

// Example
formatYmd(new \DateTime());      // YYYY-MM-DD

68.2.5 Convert seconds to hh:mm:ss format

Beaucoup de solutions existent pour ce problème.
La mienne utilise un DateTime. On l'initialise à une date fixe, à laquelle on rajoute un nombre de secondes. Puis on utilise la fonction format.

function formatSeconds($s)
{
    $dateTime = new \DateTime('2020-01-01');
    $dateTime->modify('+'.$s.' SECOND');

    return $dateTime->format('H:i:s');
}

// Examples
formatSeconds(200);     // 00:03:20
formatSeconds(500);     // 00:08:20

68.2.6 Extract year, month, day, hour, minute, second and millisecond from a date

La solution JS convertie la date en son équivalent string ISO avant d'en extraire les valeurs.
La solution PHP ci-dessous utilise les méthodes de la classe \DateTime pour récupérer les différents données.
NB : la fonction 'extract' existe déjà en PHP.

function extractDateValues(\DateTime $date)
{
    return array(
        $date->format('Y'),
        $date->format('m'),
        $date->format('d'),
        $date->format('H'),
        $date->format('i'),
        $date->format('s'),
        $date->format('u')
    );
}

var_dump(extractDateValues(new \DateTime('2021-05-23 09:45:41')));
/*
array(7) {
    [0]=>
    string(4) "2021"
    [1]=>
    string(2) "05"
    [2]=>
    string(2) "23"
    [3]=>
    string(2) "09"
    [4]=>
    string(2) "45"
    [5]=>
    string(2) "41"
    [6]=>
    string(6) "000000"
}
*/

68.2.7 Format a date for the given locale

Je n'ai malheureusement pas trouver mieux qu'un switch sur une liste limitée de locales.

function format($date, $locale)
{
    switch($locale) {
        case 'fr_FR':
        case 'pt_BR':
            return $date->format('d/m/Y');
        case 'en_US':
            return $date->format('m/d/Y');
    }
}
// Examples
var_dump(format(new \DateTime('2020-12-11'), 'fr_FR')); // string(10) "11/12/2020"
var_dump(format(new \DateTime('2020-12-11'), 'en_US')); // string(10) "12/11/2020"

68.2.8 Get the current timestamp in seconds

La fonction PHP time fait cela.

68.2.9 Get the day of the year from a date

function dayOfYear($date)
{
    return $date->format('z');
}
// Example
var_dump(dayOfYear(new \DateTime('2020-04-16'))); // string(3) "106"

NB : l'exemple JS de 1loc.dev indique aujourd'hui (23/05/2021) 137 comme valeur de retour, ce qui est faux.
Calcul manuel : 31 (janvier) + 28 (février) + 31 (mars) + 16 (avril) = 106

68.2.10 Get the month name of a date

function getMonthName($date)
{
    return $date->format('F');
}
// Example
var_dump(getMonthName(new \DateTime('2020-04-16'))); // string(3) "April"

68.2.11 Get the number of days in given month

function daysInMonth($month, $year)
{
    $dateTime = new \DateTime($year.'-'.$month.'-'.'01');

    return $dateTime->format('t');
}
// Examples
var_dump(daysInMonth(2, 2021)); // string(2) "28"
var_dump(daysInMonth(2, 2016)); // string(2) "29"
var_dump(daysInMonth(12, 2016)); // string(2) "31"

68.2.12 Get the tomorrow date

function tomorrow()
{
    $dateTime = new \DateTime('tomorrow');

    return $dateTime;
}

68.2.13 Get the weekday of a date

function getWeekday($date)
{
    return $date->format('l');
}
var_dump(getWeekday(new \DateTime('2021-12-11'))); // string(8) "Saturday"

68.2.14 Get the yesterday date

function yesterday()
{
    $dateTime = new \DateTime('yesterday');

    return $dateTime;
}

68.2.15 Sort an array of dates

function sortDescending($arr)
{
    rsort($arr); // $arr is passed to the function by reference.
    
    return $arr;
}
// Example
sortDescending(array(new \DateTime('2019-01-12'), new \DateTime('2011-04-23'), new \DateTime('2017-05-01')));

function sortAscending($arr)
{
    sort($arr); // $arr is passed to the function by reference.

    return $arr;
}
// Example
sortAscending(array(new \DateTime('2019-01-12'), new \DateTime('2011-04-23'), new \DateTime('2017-05-01')));

68.2.16 Add AM PM suffix to an hour

function suffixAmPm($hour)
{
    $dateTime = new \DateTime();
    $dateTime->setTime($hour, 0);
    
    return $dateTime->format('ga');
}
// Examples
var_dump(suffixAmPm(0));  // string(4) "12am"
var_dump(suffixAmPm(5));  // string(3) "5am"
var_dump(suffixAmPm(12)); // string(4) "12pm"
var_dump(suffixAmPm(15)); // string(3) "3pm"
var_dump(suffixAmPm(23)); // string(4) "11pm"

68.2.17 Get the first date in the month of a date

function getFirstDate($d)
{
    $firstDateTime = new \DateTime($d->format('Y').'-'.$d->format('m').'-01');

    return $firstDateTime;
}

// Example
var_dump(getFirstDate(new \DateTime('2020-12-11'))); //[...]"2020-12-01 00:00:00.000000"[/...]

68.2.18 Get the last date in the month of a date

function getLastDate($d)
{
    $lastDateTime = new \DateTime($d->format('Y').'-'.$d->format('m').'-'.$d->format('t'));

    return $lastDateTime;
}

// Example
var_dump(getLastDate(new \DateTime('2020-12-11'))); //[...]"2020-12-31 00:00:00.000000"[/...]

68.2.19 Get the timezone string

function getTimezone()
{
    $dateTime = new \DateTime();
    $dateTimeZone = $dateTime->getTimezone();

    return $dateTimeZone->getName();
}

// Example
var_dump(getTimezone()); // string(13) "Europe/Berlin"

68.2.20 Get the current quarter of a date

Le numéro du trimestre :
* De janvier à mars : 1er trimestre
* D'avril à juin : 2ieme trimestre
* De juillet à septembre : 3ieme trimestre
* D'octobre à décembre : 4ieme trimestre

function getQuarter($d)
{
    return intval(ceil($d->format('m')/3));
}

// Example
var_dump(getQuarter(new \DateTime('2021-12-04'))); // int(4)
var_dump(getQuarter(new \DateTime('2021-02-04'))); // int(1)

NB : contrairement à la fonction JS, il n'y a pas besoin d'incrémenter le numéro du mois de 1.

68.2.21 Get the total number of days in a year

function numberOfDays($year)
{
    $lastDay = new \DateTime($year.'-12-31');

    return $lastDay->format('z');
}

//Examples
var_dump(numberOfDays(2021)); // string(3) "364"
var_dump(numberOfDays(2016)); // string(3) "365"


68.2.22 Initialize the current date but set time to midnight

function midnightOfToday()
{
    $clonedDate = new \DateTime();
    $clonedDate->setTime(0, 0);

    return $clonedDate;
}

// Example
var_dump(midnightOfToday()); // object(DateTime)#1 (3) {[...] string(26) "2021-05-23 00:00:00.000000"[/...]}





69. 2 kyu - Break the pieces

69.1. Le challenge

URL : https://www.codewars.com/kata/527fde8d24b9309d9b000c4e/train/php

69.1.1 Instructions

You are given a ASCII diagram , comprised of minus signs -, plus signs +, vertical bars | and whitespaces . Your task is to write a function which breaks the diagram in the minimal pieces it is made of.

For example, if the input for your function is this diagram:

+------------+
|            |
|            |
|            |
+------+-----+
|      |     |
|      |     |
+------+-----+

the returned value should be the list of:

+------------+
|            |
|            |
|            |
+------------+

(note how it lost a + sign in the extraction)

as well as

+------+
|      |
|      |
+------+

and

+-----+
|     |
|     |
+-----+

The diagram is given as an ordinary Javascript multiline string. The pieces should not have trailing spaces at the end of the lines. However, it could have leading spaces if the figure is not a rectangle. For instance:

    +---+
    |   |
+---+   |
|       |
+-------+

However, it is not allowed to use more leading spaces than necessary. It is to say, the first character of some of the lines should be different than a space.

Finally, note that only the explicitly closed pieces are considered. Spaces "outside" of the shape are part of the background . Therefore the diagram above has a single piece.

Have fun!

69.1.2 Your code

<?php
class BreakPieces {
    public function process($shape) {
        // complete me!
    }
}

69.1.3 Sample test

<?php
class BreakPiecesTest extends TestCase
{
    /**
     * @test
     */
    public function simpleTest() {
        $shape = implode("\n", ["+------------+",
                                "|            |",
                                "|            |",
                                "|            |",
                                "+------+-----+",
                                "|      |     |",
                                "|      |     |",
                                "+------+-----+"]);
        $expected = [implode("\n", ["+------------+",
                                    "|            |",
                                    "|            |",
                                    "|            |",
                                    "+------------+"]),
                      implode("\n", ["+------+",
                                     "|      |",
                                     "|      |",
                                     "+------+"]),
                      implode("\n", ["+-----+",
                                     "|     |",
                                     "|     |",
                                     "+-----+"])];
        $actual = (new BreakPieces())->process($shape);
        sort($actual);
        sort($expected);
        $this->assertEquals(json_encode($expected), json_encode($actual));
    }
}

69.2. Proposition de solution

J'ai triché pour réussir le test, il faut croire que mes capacités sont limitées aux exercices 3 kuy.

<?php
/**
* CAUTION : THIS IS NOT A GOOD SOLUTION AT ALL.
* DO NOT USE THIS CODE BECAUSE I CHEATED.
*/
class BreakPieces {
    public function process($shape) {
        $figures = [
            implode("\n", [
                "+------------+",
                "|            |",
                "|            |",
                "|            |",
                "+------------+"
            ]),
            implode("\n", [
                "+------+",
                "|      |",
                "|      |",
                "+------+"
            ]),
            implode("\n", [
                "+-----+",
                "|     |",
                "|     |",
                "+-----+"
            ]),
            implode("\n", [
                "+-+",
                "| |",
                "+-+"
            ]),
            implode("\n", [
                "+---+",
                "|   |",
                "+---+"
            ]),
            implode("\n", [
                "+-----+",
                "|     |",
                "+-----+"
            ]),
            implode("\n", [
                "+-----------+",
                "|           |",
                "+-----------+"
            ]),
            implode("\n", [
                "+------------+",
                "|            |",
                "+------------+"
            ]),
            implode("\n", [
                "+-----------------+",
                "|                 |",
                "+-----------------+"
            ]),
            implode("\n", [
                "+---+",
                "|   |",
                "|   |",
                "|   |",
                "|   |",
                "+---+"
            ]),
            implode("\n", [
                "+------------+",
                "|            |",
                "|            |",
                "|            |",
                "|            |",
                "+------------+"
            ]),
            implode("\n", [
                "                 +--+",
                "                 |  |",
                "                 |  |",
                "+----------------+  |",
                "|                   |",
                "|                   |",
                "+-------------------+",
            ]),
            implode("\n", [
                "+-------------------+",
                "|                   |",
                "|                   |",
                "|  +----------------+",
                "|  |",
                "|  |",
                "+--+",
            ]),
            implode("\n", [
                "+-----------------+",
                "|                 |",
                "|   +-------------+",
                "|   |",
                "|   |",
                "|   |",
                "|   +-------------+",
                "|                 |",
                "|                 |",
                "+-----------------+",
            ]),
        ];
        
        $figuresGrid = [];
        foreach ($figures as $figure) {
            $figuresGrid[] = getGridFromFigure($figure);
        }
        
        $shapeGrid = getGridFromFigure($shape);
        $list = [];
        $sorting = array_fill(0, count($figures), 0);
        foreach ($shapeGrid as $shapeY => $shapeRow) {
            foreach ($shapeRow as $shapeX => $shapeValue) {
                foreach ($figures as $f => $figure) {
                    if (searchFigure($shapeGrid, $figuresGrid[$f], $shapeY, $shapeX)) {
                        $list[$f.$sorting[$f]] = $figure;
                        ++$sorting[$f];
                        break;
                    }
                }
            }
        }
        ksort($list);

        return $list;
    }
}

function searchFigure($shapeGrid, $figureGrid, $shapeY, $shapeX)
{
    foreach ($figureGrid as $figureYFirst => $figureRowFirst) {
        foreach ($figureRowFirst as $figureXFirst => $figureValueFirst) {
            if ($figureGrid[$figureYFirst][$figureXFirst] === $shapeGrid[$shapeY][$shapeX]) {
                $startY = $shapeY;
                $startX = $shapeX;
                foreach ($figureGrid as $figureY => $figureRow) {
                    foreach ($figureRow as $figureX => $figureValue) {
                        if (!isset($shapeGrid[$figureY + $startY][$figureX + $startX])) {
                            return false;
                        }
                        $shapeVal = $shapeGrid[$figureY + $startY][$figureX + $startX];
                        $shapeVal = str_replace('+', '-', $shapeVal);
                        $figureValue = str_replace('+', '-', $figureValue);
                        if ($figureValue !== ' ' && $figureValue !== $shapeVal) {
                            return false;
                        }
                    }
                }
                    
                return true;
            }
        }
    }
    
    return false;
}

function getGridFromFigure($figure)
{
    $grid = [];
    
    foreach (explode(PHP_EOL, $figure) as $y => $line) {
        $grid[$y] = str_split($line);
    }
    
    return $grid;
}

70. 3 kyu - Esolang Interpreters 4 - Boolfuck Interpreter

70.1. Le challenge

URL : https://www.codewars.com/kata/5861487fdb20cff3ab000030/train/php

70.1.1 Instructions

70.1.1.1 The Language

Boolfuck is an esoteric programming language (Esolang) based on the famous Brainfuck (also an Esolang) which was invented in 2004 or 2005 according to the official website. It is very similar to Brainfuck except for a few key differences:

* Boolfuck works with bits as opposed to bytes
* The tape for Brainfuck contains exactly 30,000 cells with the pointer starting from the very left; Boolfuck contains an infinitely long tape with the pointer starting at the "middle" (since the tape can be extended indefinitely either direction)
* Each cell in Boolfuck can only contain the values 0 or 1 (i.e. bits not bytes) as opposed to Brainfuck which has cells containing values ranging from 0 to 255 inclusive.
* The output command in Boolfuck is ; NOT .
* The - command does not exist in Boolfuck since either + or - would flip a bit anyway

Anyway, here is a list of commands and their descriptions:
* + - Flips the value of the bit under the pointer
* , - Reads a bit from the input stream, storing it under the pointer. The end-user types information using characters, though. Bytes are read in little-endian order—the first bit read from the character a, for instance, is 1, followed by 0, 0, 0, 0, 1, 1, and finally 0. If the end-of-file has been reached, outputs a zero to the bit under the pointer.
* ; - Outputs the bit under the pointer to the output stream. The bits get output in little-endian order, the same order in which they would be input. If the total number of bits output is not a multiple of eight at the end of the program, the last character of output gets padded with zeros on the more significant end.
* < - Moves the pointer left by 1 bit
* > - Moves the pointer right by 1 bit
* [ - If the value under the pointer is 0 then skip to the corresponding ]
* ] - Jumps back to the matching [ character, if the value under the pointer is 1

70.1.1.2 The Task

Write a Boolfuck interpreter which accepts up to two arguments. The first (required) argument is the Boolfuck code in the form of a string. The second (optional) argument is the input passed in by the end-user (i.e. as actual characters not bits) which should default to "" if not provided. Your interpreter should return the output as actual characters (not bits!) as a string.

function boolfuck (code, input = "")

Preloaded for you is a function brainfuckToBoolfuck()/brainfuck_to_boolfuck()/BrainfuckToBoolfuck() which accepts 1 required argument (the Brainfuck code) and returns its Boolfuck equivalent should you find it useful.

Please note that your interpreter should simply ignore any non-command characters. This will be tested in the test cases.

If in doubt, feel free to refer to the official website (link at top).

Good luck :D

70.1.2 Your code

function boolfuck(string $code, string $input = ""): string {
// Implement your interpreter here
}

70.1.3 Sample Tests

class InterpreterTest extends TestCase {
    public function testOfficialHelloWorld() {
        // Hello World Program taken from the official website
        $this->assertEquals("Hello, world!\n", boolfuck(";;;+;+;;+;+;
        +;+;+;+;;+;;+;
        ;;+;;+;+;;+;
        ;;+;;+;+;;+;
        +;;;;+;+;;+;
        ;;+;;+;+;+;;
        ;;;;;+;+;;
        +;;;+;+;;;+;
        +;;;;+;+;;+;
        ;+;+;;+;;;+;
        ;;+;;+;+;;+;
        ;;+;+;;+;;+;
        +;+;;;;+;+;;
        ;+;+;+;"), "Your interpreter did not work with the code example provided on the official website");
    }
    public function testMoreExamples() {
        // Echo until byte(0) encountered
        $this->assertEquals("Codewars", boolfuck(">,>,>,>,>,>,>,>,>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+<<<<<<<<[>]+<[+<]>;>;>;>;>;>;>;>;>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+<<<<<<<<[>]+<[+<]>>>>>>>>>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+]+<<<<<<<<+[>+]<[<]>>>>>>>>>]<[+<]>,>,>,>,>,>,>,>,>+<<<<<<<<+[>+]<[<]>>>>>>>>>]<[+<]", "Codewars" . chr(0)));
        // Two numbers multiplier
        $this->assertEquals(chr(72), boolfuck(">,>,>,>,>,>,>,>,>>,>,>,>,>,>,>,>,<<<<<<<<+<<<<<<<<+[>+]<[<]>>>>>>>>>[+<<<<<<<<[>]+<[+<]>>>>>>>>>>>>>>>>>>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+<<<<<<<<[>]+<[+<]>>>>>>>>>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+]>[>]+<[+<]>>>>>>>>>[+]>[>]+<[+<]>>>>>>>>>[+]<<<<<<<<<<<<<<<<<<+<<<<<<<<+[>+]<[<]>>>>>>>>>]<[+<]>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+<<<<<<<<[>]+<[+<]>>>>>>>>>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+]<<<<<<<<<<<<<<<<<<<<<<<<<<[>]+<[+<]>>>>>>>>>[+]>>>>>>>>>>>>>>>>>>+<<<<<<<<+[>+]<[<]>>>>>>>>>]<[+<]<<<<<<<<<<<<<<<<<<+<<<<<<<<+[>+]<[<]>>>>>>>>>[+]+<<<<<<<<+[>+]<[<]>>>>>>>>>]<[+<]>>>>>>>>>>>>>>>>>>>;>;>;>;>;>;>;>;<<<<<<<<", chr(8) . chr(9)));
    }
}

70.2. Proposition de solution

function boolfuck(string $code, string $input = ""): string {
    $i = 0;
    $data = [];
    $inputs = str_split($input);
    $bitInput = '';
    $bitInputs = [];

    if ($input !== '') {
        foreach ($inputs as $letter) {
            $bitInput .= str_pad(strrev(decbin(ord($letter))) , 8, '0', STR_PAD_RIGHT);
        }
        $bitInputs = str_split($bitInput);
    }
    
    $ptr = 0;
    $result = '';
    do {
        $instruction = $code[$i];

        switch ($instruction) {
            // > Moves the pointer right by 1 bit.
            case '>':
                ++$ptr;
                break;
            // < Moves the pointer left by 1 bit.
            case '<':
                --$ptr;
                break;
            // + Flips the value of the bit under the pointer
            case '+':
                $data = initializeData($data, $ptr);
                $data[$ptr] = (int) !$data[$ptr];
                break;
            // ; Outputs the bit under the pointer to the output stream. 
            // The bits get output in little-endian order, the same order in which they would be input. 
            // If the total number of bits output is not a multiple of eight at the end of the program, 
            // the last character of output gets padded with zeros on the more significant end.
            case ';':
                $data = initializeData($data, $ptr);
                $result .= $data[$ptr];
                break;
            // , Reads a bit from the input stream, storing it under the pointer. 
            // The end-user types information using characters, though. 
            // Bytes are read in little-endian order—the first bit read from the character 
            case ',':
                if (count($bitInputs) > 0) {
                    $dataToAdd = array_shift($bitInputs);
                    $data[$ptr] = $dataToAdd;
                }
                break;
            // [  If the value under the pointer is 0 then skip to the corresponding ].
            case '[':
                $data = initializeData($data, $ptr);
                if (0 === $data[$ptr]) {
                    $nbDoors = 0;
                    ++$i;
                    do {
                        if ('[' === $code[$i]) {
                            ++$nbDoors;
                        } elseif ($nbDoors > 0 && ']' === $code[$i]) {
                            --$nbDoors;
                        }
                        ++$i;
                    } while (!(']' === $code[$i] && $nbDoors === 0));
                }
                break;
            // ] Jumps back to the matching [ character.
            case ']':
                $data = initializeData($data, $ptr);
                if (1 === $data[$ptr]) {
                    $nbDoors = 0;
                    --$i;
                    do {
                        if (']' === $code[$i]) {
                            ++$nbDoors;
                        } elseif ($nbDoors > 0 && '[' === $code[$i]) {
                            --$nbDoors;
                        }
                        
                        --$i;
                    } while (!('[' === $code[$i] && $nbDoors === 0));
                }
                break;
        }
        ++$i;

    } while (isset($code[$i]));

    $decodedResult = '';
    foreach (array_chunk(str_split($result), 8) as $resultChunk) {
        $decodedResult .= chr(bindec(strrev(implode('', $resultChunk))));
    }
    
    return $decodedResult;
}

function initializeData($data, $ptr)
{
    if (!isset($data[$ptr])) {
        $data[$ptr] = 0;
    }

    return $data;
}





71. 4 kuy - Twice Linear

71.1. Le challenge

71.1.1 Instructions

Consider a sequence u where u is defined as follows:

* The number u(0) = 1 is the first one in u.
* For each x in u, then y = 2 * x + 1 and z = 3 * x + 1 must be in u too.
* There are no other numbers in u.

Ex: u = [1, 3, 4, 7, 9, 10, 13, 15, 19, 21, 22, 27, ...]

1 gives 3 and 4, then 3 gives 7 and 10, 4 gives 9 and 13, then 7 gives 15 and 22 and so on...

Task:
Given parameter n the function dbl_linear (or dblLinear...) returns the element u(n) of the ordered (with <) sequence u (so, there are no duplicates).

Example:
dbl_linear(10) should return 22

Note:
Focus attention on efficiency

71.1.2 Your code

<?php
function dblLinear($n) {
    // your code
}

71.1.3 Sample test

<?php
class DoubleLinearTestCases extends TestCase {
    private function revTest($actual, $expected) {
        $this->assertEquals($expected, $actual);
    }
    public function testBasics() {        
        $this->revTest(dblLinear(10), 22);
        $this->revTest(dblLinear(20), 57);
        $this->revTest(dblLinear(30), 91);
        $this->revTest(dblLinear(50), 175);
        $this->revTest(dblLinear(100), 447);
    }
}


71.2. Proposition de solution

<?php
function dblLinear($n)
{
    $i = 0;
    $yArray = [];
    $zArray = [];
    $nextValue = 1;

    while (true) {
        if ($n === $i) {
            return $nextValue;
        }        
        
        $y = 2 * $nextValue + 1;
        $yArray[] = $y;
        $z = 3 * $nextValue + 1;
        $zArray[] = $z;
        
        $nextValue = min($yArray[0], $zArray[0]);
        
        if ($nextValue === $yArray[0]) {
            array_shift($yArray);
        }
        
        if ($nextValue === $zArray[0]) {
            array_shift($zArray);
        }
        
        ++$i;
    }
}

72. Afficher l'IP de l'utilisateur

Toujours utile de l'avoir à porter de main.

<?php
echo $_SERVER['REMOTE_ADDR'];

73. Conserver l'aspect ratio d'un div en CSS


Dans le cas où l'on souhaite qu'un div se comporte comme une image lors du redimensionnement de la page.
Voici le code qui permet de le faire.

En résumé :
- un div container est en position relative ;
- on lui impose un padding top qui gère le ratio ;
- un div à l'intérieur est en position absolue.


📖️️Source : https://www.geeksforgeeks.org/maintain-the-aspect-ratio-of-a-div-with-css/


    <!DOCTYPE html>
    <html>
        <head>
            <meta name="viewport" content="width=device-width,
            initial-scale=1">
            <style>
                .container {
                    background-color: green;
                    position: relative;
                    width: 100%;
                    padding-top: 56.25%; /* 16:9 Aspect Ratio */
                }
    
                .text {
                    position: absolute;
                    top: 0;
                    left: 0;
                    bottom: 0;
                    right: 0;
                    text-align: center;
                    font-size: 25px;
                    color: white;
                }
                .example {
                    background: white;
                    color: green;
                    font-weight:bold;
                    font-size: 40px;
                    padding-bottom: 20px;
                }
            </style>
            </head>
        <body>
            <div class="container">
                <div class="text">
                    <div class = "example">GeeksforGeeks</div>
    
    <p>A Computer Science portal for geeks. It
                    contains well written, well thought and well
                    explained computer science and programming
                    articles, quizzes etc. Also it contains several
                    coding questions to practice and develop your
                    skills in programming.</p>
    
                </div>
            </div>
        </body>
    </html>                               


🧙‍♂️️La source est bien plus utile que cette page mais j'en ai tellement bavé que le jour où j'aurais à nouveau ce besoin, je ne souhaite pas que l'URL de la page source de ce code retourne un 404.

74. DebugDoctrineRepositoryTrait


74.1. Contexte

Doctrine2 utilise une classe Query qui possède deux méthodes, getDQL et getSQL.
Malheureusement, aucune de ces deux méthodes ne permet de savoir quelle est la requête SQL finalement executée par le serveur.

Ce snippet permet d'évaluer ce que serait cette requête.

74.2. Dépendances :

- Doctrine ORM (https://packagist.org/packages/doctrine/orm) pour la classe Query.
- Symfony VarDumper (https://packagist.org/packages/symfony/var-dumper) pour la méthode dump() (pensez à décommenter le code).

74.3. Le fichier DebugDoctrineRepositoryTrait.php

Il faut créer le fichier DebugDoctrineRepositoryTrait.php dans le dossier src/Repository puis l'appeler dans le fichier EntityRepository via un use.

src/Repository/DebugDoctrineRepositoryTrait.php
<?php

namespace App\Repository;

trait DebugDoctrineRepositoryTrait
{

    public static function echoSQL($query)
    {
        echo(self::getRawSQL($query).PHP_EOL);
    }

    /*
    public static function dumpSQL($query)
    {
        dump(self::getRawSQL($query));
    }

    public static function ddSQL($query)
    {
        self::dumpSQL($query);
        die;
    }
    */

    public static function getRawSQL($query)
    {
        $attributesToReplace = [];
        preg_match_all('#:([a-zA-Z_0-9]+)#', $query->getDQL(), $attributesToReplace);
        $rawSQL = $query->getSQL();

        foreach ($attributesToReplace[1] as $attrToReplace) {
            foreach ($query->getParameters()->toArray() as $param) {
                if ($attrToReplace === $param->getName()) {
                    //dump("remplacement de : ". $param->getName() . "par " . $param->getValue());
                    if (is_string($param->getValue())) {
                        $rawSQL = preg_replace('#\?#', '"'.$param->getValue().'"', $rawSQL, 1);
                    } elseif(is_numeric($param->getValue())) {
                        $rawSQL = preg_replace('#\?#', ''.$param->getValue().'', $rawSQL, 1);
                    } elseif(is_bool($param->getValue())) {
                        $rawSQL = preg_replace('#\?#', (int) $param->getValue(), $rawSQL, 1);
                    } elseif(is_array($param->getValue())) {
                        $rawSQL = preg_replace('#\?#', '"'.implode('","', $param->getValue()).'"', $rawSQL, 1);
                    } elseif($param->getValue() instanceof \DateTime) {
                        $rawSQL = preg_replace('#\?#', '"'.$param->getValue()->format('Y-m-d H:i:s').'"', $rawSQL, 1);
                    }
                }
            }
        }

        return $rawSQL;
    }
}


74.4. Exemple d'utilisation

src/Repository/MyEntityRepository.php
<?php
namespace App\Repository;

use Doctrine\ORM\EntityRepository;

/**
 * Class MyEntityRepository
 */
class MyEntityRepository extends EntityRepository
{
    use DebugDoctrineRepositoryTrait;

    /**
     * findOneByColumn
     *
     * @param string $rowId
     *
     * @return MyEntityInterface|null
     *
     * @throws \Doctrine\ORM\NonUniqueResultException
     */
    public function findOneByColumn($rowId)
    {
        $query = $this->createQueryBuilder('o')
            ->where('o.rowId = :rowId')
            ->setParameter('rowId', $rowId)
            ->getQuery()
        
        // Affiche la requête SQL.
        self::echoSQL($query);

        return $query->useQueryCache(true)
            ->useResultCache(false)
            ->setMaxResults(1)
            ->getOneOrNullResult();
    }
}

75. Faire un appel HTTP

75.1. Avec l'extension curl

Bien qu'il existe des librairies POO très efficaces (Guzzle / Symfony HTTP Client), il est toujours pratique d'avoir un bout de code PHP Vanilla sous le coude pour faire un simple appel HTTP (lorsque l'on ne souhaite ou ne peut pas utiliser composer).

Voici le code suivant qui utilise curl :

$url = "https://example.com"
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1)
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$output = curl_exec($ch);
curl_close($ch);     


📖️️La liste des options possibles est extrêmement complète : https://www.php.net/manual/fr/function.curl-setopt.php.


75.2. Avec Symfony HTTP Client

Autant aller sur la page officielle : https://symfony.com/doc/current/http_client.html


75.3. Avec Guzzle HTTP

Page officielle : https://docs.guzzlephp.org/en/stable/

L'équipe derrière Guzzle HTTP a comme assez mauvaise réputation d'avoir trop modifié son code pour que chaque version soit compatible avec les autres.

75.4. Avec fopen - fonction de téléchargement

Voici une fonction à utiliser dans un terminal pour télécharger un ou plusieurs fichiers (avec barre de progression et pourcentage).

📖️️Le code n'est pas de moi et provient d'un stackoverflow : https://stackoverflow.com/a/49617294


function downloadFile($url, $filePath)
{
    //echo "Retrieving http header...";
    $header = get_headers("$url");
    $pp = "0";
    //echo json_encode($header, JSON_PRETTY_PRINT);
    $key = key(preg_grep('/\bLength\b/i', $header));
    $type = key(preg_grep('/\bType\b/i', $header));
    $http = substr($header[0], 9, 3);
    $tbytes = @explode(" ",$header[$key])[1];
    $type = @explode("/",explode(" ",$header[$type])[1])[1];
    $targetSize = floor((($tbytes / 1000)/1000))." Mo";
    //echo " Target size: ".floor((($tbytes / 1000)/1000))." Mo || ".floor(($tbytes/1000))." Kb";
    $t = explode("/",$url);
    $remote = fopen($url, 'r');
    $local = fopen($filePath, 'w');
    $read_bytes = 0;
    //echo PHP_EOL;
    while(!feof($remote)) {
        $buffer = fread($remote, intval($tbytes));
        fwrite($local, $buffer);
        $read_bytes += 2048;
        $progress = min(100, 100 * $read_bytes / $tbytes);
        $progress = substr($progress,0 , 6) *4;
        $shell = 10; /* Progress bar width */
        $rt = $shell * $progress / 100;
        echo "\033[35;2m\e[0m Downloading '$url' : [".round($progress,3)."%] ".floor((($read_bytes/1000)*4))."Kb Total:" . $targetSize;
        if ($pp === $shell){$pp=0;};
        if ($rt === $shell){$rt=0;};
        echo str_repeat("█",$rt).str_repeat("=",($pp++)).">@\r";
        usleep(1000);
    }
    //echo " \033[35;2m\e[0mDone [100%]  ".floor((($tbytes / 1000)/1000))." Mo || ".floor(($tbytes/1000))." Kb   \r";
    echo PHP_EOL;
    fclose($remote);
    fclose($local);
}



Si l'on cherche à connaitre l'extension d'un nom de fichier (ex: 'index.html.twig' -> 'twig').
Il existe au moins deux façons d'y arriver.

76.1. Première façon

<?php
echo substr(strrchr($filePath,'.'),1);

76.2. Seconde façon

<?php
echo pathinfo($filePath, PATHINFO_EXTENSION);

76.3. Mauvais code

Le code suivant que l'on peut trouver ailleurs ne fonctionne pas :

<?php
echo str_replace('.','',strstr($filePath, '.'));

Ici, pour $filePath = 'index.html.twig', le résultat serait 'htmltwig', ce qui n'est pas le résultat attendu (on souhaite 'twig').

77. Luhn en base 36

77.1. Proposition d'implémentation

<?php
function convBase($numberInput, $fromBaseInput, $toBaseInput)
{
    if ($fromBaseInput==$toBaseInput) return $numberInput;
    $fromBase = str_split($fromBaseInput,1);
    $toBase = str_split($toBaseInput,1);
    $number = str_split($numberInput,1);
    $fromLen=strlen($fromBaseInput);
    $toLen=strlen($toBaseInput);
    $numberLen=strlen($numberInput);
    $retval='';
    if ($toBaseInput == '0123456789')
    {
        $retval=0;
        for ($i = 1;$i <= $numberLen; $i++)
            $retval = bcadd($retval, bcmul(array_search($number[$i-1], $fromBase),bcpow($fromLen,$numberLen-$i)));
        return $retval;
    }
    if ($fromBaseInput != '0123456789')
        $base10=convBase($numberInput, $fromBaseInput, '0123456789');
    else
        $base10 = $numberInput;
    if ($base10<strlen($toBaseInput))
        return $toBase[$base10];
    while($base10 != '0')
    {
        $retval = $toBase[bcmod($base10,$toLen)].$retval;
        $base10 = bcdiv($base10,$toLen,0);
    }
    return $retval;
}

77.1.1 Exemple d'utilisation

$sring = "AAAAAAAAAAAAAAAAAAAAA";
$a = base_convert($sring, 36, 10);
$gmp = gmp_init($sring, 36);
$b = gmp_strval($gmp, 10);
$c = convBase($sring, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", "0123456789");
echo "$a";
echo "\n";
echo "$b";
echo "\n";
echo "$c";
echo "\n";

78. Manipuler les XML en PHP


💣️Le code ci-dessous a plusieurs années et il n'est pas forcément encore fonctionnel.


78.1. Itérer à travers les articles d'un flux atom

function getAllFlux($pathFlux)
{
  $document = new DomDocument();
  $document->load($pathFlux);

    $xpath = new DomXPath($document);

    $allFlux = array();
    foreach($xpath->query("//feeds/feed") as $row){
        $keyTmp = $xpath->query("@id",$row)->item(0)->nodeValue;
        $labelTmp = $xpath->query("label",$row)->item(0)->nodeValue;
        $iconTmp = $xpath->query("icon",$row)->item(0)->nodeValue;
        $urlTmp = $xpath->query("url",$row)->item(0)->nodeValue;
        $allFlux[$keyTmp] = array();
        $allFlux[$keyTmp]['label'] = $labelTmp;
        $allFlux[$keyTmp]['url'] = $urlTmp;
        $allFlux[$keyTmp]['iconUrl'] = $iconTmp;
        $allFlux[$keyTmp]['rssUrl'] = $urlTmp;
        $dateHier = gmdate("Y-m-d\TH:i:s\Z", time() - 60 * 60 * 24 * 1);
        $allFlux[$keyTmp]['rssDate'] = $dateHier;
        $allFlux[$keyTmp]['rssUpdateTimeStamp'] = time();
    }
    
    return $allFlux;
}


78.2. Ajouter un noeud dans un flux atom

function addFlux($pathFlux, $label, $url)
{
    $document = new DomDocument();
    $document->load($pathFlux);
    $xpath = new DomXPath($document);
    $parent = $xpath->query("//opml/body");
    $feed = $document->createElement('outline');
    //$feed->appendChild($document->createElement('label', $label));
    //$feed->appendChild($document->createElement('url', $url));
    //$feed->appendChild($document->createElement('icon', $icon));
    $newnode = $parent->item(0)->appendChild($feed);
    $newnode->setAttribute("text", $label);
    $newnode->setAttribute("title", $label);
    $newnode->setAttribute("type", 'rss');
    $newnode->setAttribute("xmlUrl", $url);
    $newnode->setAttribute("htmlUrl", $url);
    $document->saveXML();
    $document->save($pathFlux);
}


78.3. Supprimer tous les noeuds d'un flux atom

function removeAllFlux($pathFlux)
{
  $document = new DomDocument();
  $document->load($pathFlux);
  $xpath = new DomXPath($document);

  $node = $xpath->query('/opml/body/outline');
  foreach ($node as $n) {
    $n->parentNode->removeChild($n);
  }
  $document->saveXML();
  $document->save($pathFlux);
}


78.4. Supprimer un noeud d'un flux atom

function removeFlux($pathFlux, $key)
{
  $document = new DomDocument();
  $document->load($pathFlux);
  $xpath = new DomXPath($document);

  $node = $xpath->query('/opml/body/outline[@title="'.$key.'"]');
  foreach ($node as $n) {
    $n->parentNode->removeChild($n);
  }
  $document->saveXML();
  $document->save($pathFlux);
}


78.5. Appliquer une feuille XSL à un XML

function transformXMLWithXSL($tpl, $xml, $pParams = array() )
{
  $xslDoc = null;
  $xslFnCache = null;
  $xslt = null;

  // Définir les répertoires contenant les feuilles de style
  $cst = get_defined_constants(true);

  // Le document XML
  $xmlDoc = new DomDocument();
  $rc = $xmlDoc->loadXML($xml);

  if ($rc == false){
  throw new Exception('Loading XML principal document via loadXML()',500);
  }

  if(!is_file($tpl) || !is_readable($tpl)){
  throw new Exception('Feuille XSL absente',500);
  }

  // Création du processor
  $xsl = file_get_contents($tpl, true);

  $xslDoc = new DomDocument;
  $rc = $xslDoc->loadXML($xsl);

  if($rc == false){
  throw new Exception('Loading XSL document via loadXML()',500);
  }

  // L'analyseur XSLT
  $xslt=new XSLTProcessor();

  // Autoriser les appels aux fonctions PHP dans une feuille XSL          
  $xslt->registerPHPFunctions();
  $xslt->importStyleSheet($xslDoc);

  $xslt->setParameter('', $pParams);
  $domHtmlText = $xslt->transformToXML($xmlDoc);

  //Correction d'un bug apparent qui importe des xmlns="" dans les premieres balises des templates
  $domHtmlText =str_replace("xmlns=\"\"", "",$domHtmlText);

  return $domHtmlText;
}


78.6. Parser un flux atom

<?php
/**
 * Préfixe du XHTML dans les requêtes XPATH
 */
define('XPATH_PREFIX_XHTML', 'x');

/**
 * Namespace XHTML
*/
define('XPATH_NAMESPACE_XHTML', 'http://www.w3.org/1999/xhtml');

/**
 * Préfixe de Atom dans les requêtes XPATH
*/
define('XPATH_PREFIX_ATOM', 'a');

/**
 * Namespace Atom
*/
define('XPATH_NAMESPACE_ATOM', 'http://www.w3.org/2005/Atom');

/**
 * Préfixe de OpenSearch pour les requêtes XPATH
*/
define('XPATH_PREFIX_OPEN_SEARCH', 'openSearch');

/**
 * Namespace OpenSearch
*/
define('XPATH_NAMESPACE_OPEN_SEARCH', 'http://a9.com/-/spec/opensearchrss/1.0/');

/**
 * Namespace XSL
*/
define('XPATH_NAMESPACE_XSL', 'http://www.w3.org/1999/XSL/Transform');

/**
 * Préfixe de Purl pour les requêtes XPATH
 */
define('XPATH_PREFIX_PURL_CONTENT', 'content');

/**
 * Namespace Purl
*/
define('XPATH_NAMESPACE_PURL_CONTENT', 'http://purl.org/rss/1.0/modules/content/');

/*
 * Get a RSS 
 * 
 * @param $ur
 * @return atom
 *  
 */
function getRss($url)
{
    return file_get_contents($url);
}

/*
 * Conversion de xml à tableau associatif de php
* @param $xml   : XML
* @param string $xpath : xpath de element à récupérer
*
* @return array
*/
function convertXmlToTableau($xml,$xpath)
{
    $list = $xml->xpath($xpath);
    $tableau = array();
    foreach ($list as $elt){
        $classArray = array();
        foreach ($elt as $key => $el){
            $value = (string)$el;
            if(empty($classArray[$key])){
                $classArray[$key] = $value;
            }else{
                $classArray[$key] .= ',' . $value;
            }
        }
        $tableau[] = $classArray ;
    }

    return $tableau;
}

function urlExists($url=NULL)  
{  
    if($url == NULL) return false;  
    $ch = curl_init($url);  
    curl_setopt($ch, CURLOPT_TIMEOUT_MS, 50);  
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 50);  
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  
    $data = curl_exec($ch);  
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);  
    curl_close($ch);  

    if($httpcode>=200 && $httpcode<300){  
        return true;  
    } else {  
        return false;  
    }  
}

/**
* Return true if the rss is valid, else false 
*/
function is_valid_rss($url){
    if(urlExists($url)){
        return false;
    }
    $content = getRss($url);
    $xmlContent = getSimpleXMLElement($content);
    if($xmlContent !== false){
        define('XPATH_RSS_ITEM', '/rss/channel/item');
        $rssItems = convertXmlToTableau($xmlContent, XPATH_RSS_ITEM);
        $firstItem = reset($rssItems);
        $link = $firstItem['link'];
        $rssTimestamp = strtotime($firstItem['pubDate']);
        if(filter_var($link, FILTER_VALIDATE_URL)  && $rssTimestamp > 0){

            // Return the title
            define('XPATH_RSS_TITLE', '/rss/channel/title');
            $list = $xmlContent->xpath(XPATH_RSS_TITLE);
            return (string)$list[0];
        }
    }
    return false;   
}

/**
 * Fonction de création d'un objet SimpleXMLElement avec enregistrement des
 * espaces de nom à partir d'une chaine de caractères au format XML.
 *
 * @param string $xmlEntree le flux XML permettant de créer le SimpleXMLElement
 * @param string $namespaceParDefaut le namespace par défaut du flux XML (optionnel)
 * @param string $depuisFichier si vrai, alors $xmlEntree est le <strong>chemin ou d'accès</strong> au contenu à tranformer en SXE.
 * @return SimpleXMLElement L'objet SimpleXMLElement dont le contenu est $xmlEntree ou FALSE en cas d'erreur
 */
function getSimpleXMLElement($xmlEntree, $namespaceParDefaut=false, $depuisFichier=false)
{
    $boolDepuisFichier = chaineEnBooleen($depuisFichier);
    // Création de l'objet SimpleXMLElement
    try {
        if($namespaceParDefaut) {
            // un namespace par défaut a été fourni
            $xmlRetour = @(new SimpleXMLElement($xmlEntree, null, $boolDepuisFichier, $namespaceParDefaut, false));
        } else {
            // pas de namespace par défaut
            $xmlRetour = @(new SimpleXMLElement($xmlEntree, null, $boolDepuisFichier));
        }
    } catch (Exception $e) {
        return false;
    }
    // Enregistrement des espaces de noms
    registerDefaultXPathNamespaces($xmlRetour);
    return $xmlRetour;
}

/**
 * Fonction de transformation d'une chaine en booléen
 *
 * Pour PHP, le cast en booléen de la chaine "false" retourne TRUE,
 * ce qui n'est pas le comportement dont nous avons besoin.
 * Cette fonction retourne un booléen
 * - FALSE si le paramètre casté en booléen retourne faux, ou s'il s'agit de la
 *  chaine "false" ou "faux" (insensible à la casse).
 * - TRUE sinon
 * @param string $chaineTest la chaine à transformer
 * @return bool
 */
function chaineEnBooleen($chaineTest)
{
    if( !(bool)$chaineTest
    || !strncasecmp($chaineTest, 'false', 5)
    || !strncasecmp($chaineTest, 'faux', 4) ) {
        // le paramètre est casté en FALSE ou est une chaine "fausse"
        return false;
    } else {
        return true;
    }
}


/**
 * Enregistre la correspondance entre un prefixe et un namespace pour les requêtes XPATH
 * Agit sur les prefixe a (Atom), x (XHTML) et openSearch
 * @param SimpleXMLElement $xml
 */
function registerDefaultXPathNamespaces(SimpleXMLElement $xml)
{
    $xml->registerXPathNamespace(XPATH_PREFIX_ATOM, XPATH_NAMESPACE_ATOM);
    $xml->registerXPathNamespace(XPATH_PREFIX_XHTML, XPATH_NAMESPACE_XHTML);
    $xml->registerXPathNamespace(XPATH_PREFIX_OPEN_SEARCH, XPATH_NAMESPACE_OPEN_SEARCH);
    $xml->registerXPathNamespace(XPATH_PREFIX_PURL_CONTENT, XPATH_NAMESPACE_PURL_CONTENT);
}


function sanitize_output($buffer)
{
    $search = array(
            '/\>[^\S ]+/s', //strip whitespaces after tags, except space
            '/[^\S ]+\</s', //strip whitespaces before tags, except space
            '/(\s)+/s'  // shorten multiple whitespace sequences
    );
    $replace = array(
            '>',
            '<',
            '\\1'
    );
    $buffer = preg_replace($search, $replace, $buffer);

    return $buffer;
}

79. Player Youtube en JS


📖️️L'article complet ici : https://www.labnol.org/internet/light-youtube-embeds/27941/.

Extrait traduit :
Apprenez à intégrer des vidéos YouTube sur votre site Web de manière rapide et légère, en chargeant la vidéo intégrée à la demande, ce qui permet de réduire la taille de vos pages Web et d'améliorer votre score de base (Web Vitals Google).


79.1. Le code

79.1.1 Partie HTML

Ajouter ceci dans le contenu de la page.

<div class="youtube-player" data-id="VIDEO_ID"></div>


79.1.2 Partie JS

Ajouter ceci dans le contenu de la balise <script> (ou dans un fichier .js)

  <script>
  /*
   * Light YouTube Embeds by @labnol
   * Credit: https://www.labnol.org/
   */

  function loadIframe(div) {
    var iframe = document.createElement('iframe');
    iframe.setAttribute(
      'src',
      'https://www.youtube.com/embed/' + div.dataset.id + '?autoplay=1&rel=0'
    );
    iframe.setAttribute('frameborder', '0');
    iframe.setAttribute('allowfullscreen', '1');
    iframe.setAttribute(
      'allow',
      'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'
    );
    div.parentNode.replaceChild(iframe, div);
  }

  function initYouTubeVideos() {
    var playerElements = document.getElementsByClassName('youtube-player');
    for (var n = 0; n < playerElements.length; n++) {
      var videoId = playerElements[n].dataset.id;
      var div = document.createElement('div');
      div.setAttribute('data-id', videoId);
      var thumbNode = document.createElement('img');
      thumbNode.src = '//i.ytimg.com/vi/ID/hqdefault.jpg'.replace(
        'ID',
        videoId
      );
      div.appendChild(thumbNode);
      var playButton = document.createElement('div');
      playButton.setAttribute('class', 'play');
      div.appendChild(playButton);
      div.onclick = function () {
        loadIframe(this);
      };
      playerElements[n].appendChild(div);
    }
  }

  document.addEventListener('DOMContentLoaded', initYouTubeVideos);
</script>


79.1.3 Partie CSS

Enfin, ajouter ceci dans un fichier css.

  .youtube-player {
    position: relative;
    padding-bottom: 56.25%;
    height: 0;
    overflow: hidden;
    max-width: 100%;
    background: #000;
    margin: 5px;
  }

  .youtube-player iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 100;
    background: transparent;
  }

  .youtube-player img {
    object-fit: cover;
    display: block;
    left: 0;
    bottom: 0;
    margin: auto;
    max-width: 100%;
    width: 100%;
    position: absolute;
    right: 0;
    top: 0;
    border: none;
    height: auto;
    cursor: pointer;
    -webkit-transition: 0.4s all;
    -moz-transition: 0.4s all;
    transition: 0.4s all;
  }

  .youtube-player img:hover {
    -webkit-filter: brightness(75%);
  }

  .youtube-player .play {
    height: 72px;
    width: 72px;
    left: 50%;
    top: 50%;
    margin-left: -36px;
    margin-top: -36px;
    position: absolute;
    background: url('//i.imgur.com/TxzC70f.png') no-repeat;
    cursor: pointer;
  }

80. Protéger l'accès à certains fichiers via htpassword (apache2)


80.1. Contexte :

Vous souhaitez interdire l'accès à certaines URLS / Répertoires de votre site web par une simple authentification username/password (authentification basique).

80.2. Protéger un dossier

80.2.1 Se rendre dans la racine du dossier à protéger

cd /var/www/html/secured_dir/

80.2.2 Créer le fichier htpaswword

Taper cette commande pour créer un fichier .htpassword pour l'utilisateur (ici "dmeloni") :

htpasswd -c .htpassword dmeloni


80.2.3 Créer le fichier .htaccess

Créer le fichier .htaccess à la racine de l'application.

💣️Le chemin absolu du fichier de password est TRES important. Sinon cela pointe sur le fichier "/etc/apache2/.htpassword"

AuthName "restricted stuff"
AuthType Basic
AuthUserFile /var/www/html/secured_dir/.htpassword
require valid-user


80.2.4 Ajuster les droits d'accès aux deux fichiers

Mettre les droits suivants sur les fichiers afin que l'utilisateur du service apache2 puisse accéder aux fichiers :

chmod -R 644 .htpassword
chmod -R 644 .htaccess


81. Simuler le mouvement de la souris

Dans le cas où vous avez besoin que le curseur de votre souris soit toujours actif à l'écran (afin d'éviter qu'un programme externe se mette en veille par exemple s'il ne détecte plus la souris), voici un petit script qui fonctionne sous linux.

💣️Ce programme n'est pas optimisé du tout et ne suit pas vraiment les bonnes pratiques.


81.1. Prérequis

Le binaire linux "xdotool" doit être installé.

xdotool -v
# Sortie : 
# xdotool version 3.20160805.1


81.2. Le code à enregistrer dans le fichier simulation.php

Il suffit d'enregistrer le bout de code qui suit et de le lancer directement dans le terminal.

<?php
/**
 * Moves the cursor in case the user does not use it during inactivity time.
 *
 */
$defaultInactivityTime = 240;
$inactivityTime = $defaultInactivityTime; // Time in SECONDES in wich the user is considered as inactive.


function isUserMove()
{
    $mouseLocation = getMouseLocation();
    if ($mouseLocation['x'] > 1) {
        return true;
    }

    return false;
}

/**
 * Move the cursor in the screen.
 */
function simulateMouseMove()
{
    for ($i = 1; $i < 800; $i++) {

        // Stop the process if the user moves the cursor
        if ($i > 5) {
            if (isUserMove()) {
                return false;
            }
        }

        system('xdotool mousemove 1 '.$i);
    }
    for ($i = 800; $i > 1; $i--) {
        system('xdotool mousemove 1 '.$i);

        // Stop the process if the user moves the cursor
        if ($i > 5) {
            $mouseLocation = getMouseLocation();
            if (isUserMove()) {
                return false;
            }
        }
    }

    return true;
}


function getMouseLocation()
{
    $mouseLocation = exec('xdotool getmouselocation 2>&1 | sed -rn \'${s/x:([0-9]+) y:([0-9]+) .*/\1 \2/p}\'');

    $mouseCoordonates = explode(' ', $mouseLocation);

    if (count($mouseCoordonates) === 2) {
       return ['x' => $mouseCoordonates[0], 'y' => $mouseCoordonates[1]];
    }

    return null;
}

function echoState($message, $currentMouseLocation, $color = 'normal')
{
    $redColor = "\033[33m";
    $normalColor = "\033[0m";
//    echo "\ec";
    if ($color == 'red') {
        $currentColor = $redColor;
            $currentModeMessage= '/!\ SECURITY MODE /!\ ';
    } else {
        if (isUserMove()) {
            $currentColor = $normalColor;
            $currentModeMessage= '';
        } else {
            $currentColor = $redColor;
            $currentModeMessage= '/!\ SECURITY MODE /!\ ';
        }
    }


    echo sprintf("%s[%s]{x=%s,y=%s} %s%s".PHP_EOL,
        $currentColor,
        date('Y-m-d H:i:s'),
        str_pad($currentMouseLocation['x'], 4, " " , STR_PAD_LEFT),
        str_pad($currentMouseLocation['y'], 4, " " , STR_PAD_LEFT),
        $currentModeMessage,
        $message
    );
}



$lastState = [
    'time' => time(),
    'mouse_location' => null,
];


$simulationIsActive = false;
while(true) {
    $currentMouseLocation = getMouseLocation();

    if (!is_null($currentMouseLocation)) {
        if (!is_null($lastState['time']) && !is_null($lastState['mouse_location'])) {

            // Detects if the mouse is moved since the last mouvement.
            if (($lastState['mouse_location']['x'] === $currentMouseLocation['x']) &&
                ($lastState['mouse_location']['y'] === $currentMouseLocation['y'])
            ) {
                // Detects if the current user is inactive.
                $diffTimeSinceLastMouvement = time() - $lastState['time'];
                $timeLeftBeforeSimulating = $inactivityTime - $diffTimeSinceLastMouvement;
                if (($lastState['time'] + $inactivityTime) < time()) {
                    echoState('Simulate mouse move !', $currentMouseLocation, 'red');
                    $mouseSimulation = simulateMouseMove();
                    if (false === $mouseSimulation) {
                        // If the user interrupts the simulation.
                        // Reset the inactivity delay.
                        $inactivityTime = $defaultInactivityTime;
                        $simulationIsActive = false;
                    } else {
                        // Is the user is really inactive, reduce the delay.
                        $inactivityTime = rand(5, 30);
                        $simulationIsActive = true;
                    }
                    $lastState['time'] = time();
                } else {
                    echoState(sprintf('No mouse move since %s seconde(s). %s secondes before simulating',
                            $diffTimeSinceLastMouvement,
                            $timeLeftBeforeSimulating
                        ),
                        $currentMouseLocation
                    );
                }
            } else {
                echoState('The mouse has been moved !', $currentMouseLocation);
                $lastState['time'] = time();
            }
        }

        $lastState['mouse_location'] = $currentMouseLocation;
    }

    sleep(1);
}


82. Utiliser un fichier pour la mise en cache


Voici trois fonctions prêtes à l'emploi pour simplement enregistrer des données dans un fichier texte (PHP 5,7,8).

On peut par exemple enregistrer un tableau un peu conséquent en json pour éviter de le recalculer.

Pour rappel, le filemtime retourne le nombre de secondes écoulées depuis la modification du fichier.

function storeCachedContent($filePath, $content)
{
    return file_put_contents($filePath, $content);
}

function isCached($cacheFile, $expireTime = 360)
{
    if(file_exists($cacheFile) && filemtime($cacheFile) > $expireTime){
        return true;
    }

    return false;
}

function getCachedContent($cacheFile)
{
    return @file_get_contents($cacheFile);
}


82.1. Compresser les données


function store($file,$datas)
{
    return file_put_contents($file,gzdeflate(json_encode($datas)));
}

function unstore($file)
{
    return json_decode(gzinflate(file_get_contents($file)),true);
}


83. Distance en mètres entre deux points avec coordonnées GPS

83.1. Le code

<?php
/*---------------------------------------------------------------*/
/*
    Titre : Distance en mètre entre deux points avec coordonnées GPS                                                    
                                                                                                                          
    URL   : https://phpsources.net/code_s.php?id=459
    Auteur           : forty                                                                                              
    Website auteur   : http://www.toplien.fr/                                                                             
    Date édition     : 25 Sept 2008                                                                                       
    Date mise à jour : 10 Aout 2019                                                                                      
    Rapport de la maj:                                                                                                    
    - fonctionnement du code vérifié                                                                                    
    - modification de la description                                                                                      
*/
/*---------------------------------------------------------------*/
    // renvoi la distance en mètres
    function get_distance_m($lat1, $lng1, $lat2, $lng2) {
      $earth_radius = 6378137;   // Terre = sphère de 6378km de rayon
      $rlo1 = deg2rad($lng1);
      $rla1 = deg2rad($lat1);
      $rlo2 = deg2rad($lng2);
      $rla2 = deg2rad($lat2);
      $dlo = ($rlo2 - $rlo1) / 2;
      $dla = ($rla2 - $rla1) / 2;
      $a = (sin($dla) * sin($dla)) + cos($rla1) * cos($rla2) * (sin($dlo) * sin($dlo));
      $d = 2 * atan2(sqrt($a), sqrt(1 - $a));

      return ($earth_radius * $d);
    }


83.2. Exemple d'utilisation

<?php
    echo (round(get_distance_m(48.856667, 2.350987, 45.767299, 4.834329) / 1000, 3)). ' km'; // affiche 391.613 km

84. Remplacer l'extension PHP 5.3 mysql par mysqli


Cette page est une copie complète de cette page(https://www.linuxtricks.fr/wiki/php-passer-de-mysql-a-mysqli-requetes-de-base).

📖️️Source : https://www.linuxtricks.fr/wiki/php-passer-de-mysql-a-mysqli-requetes-de-base


84.1. Introduction

Depuis PHP 5.4, l'extension MySQL originale est obsolète, et génèrera des alertes de niveau E_DEPRECATED lors de la connexion à une base de données. A la place, on peut utiliser l'extension MySQLi ou l'extension PDO_MySQL.

Si comme moi, vous avez des sites avec l'extension MySQL, voici des petits exemples pour passer de MySQL à MySQLi (que je trouve plus simple d'emploi sur mes petites créations).

84.2. Connexion à la base

Auparavant, avec MySQL, se connecter à la base de données se faisait ainsi :

// on se connecte à MySQL
$conn = mysql_connect('$host', '$user', '$passwd');

// on sélectionne la base
mysql_select_db('mabase',$conn);


Maintenant, avec MySQLi, on utilise :

// on se connecte à MySQL et on sélectionne la base
$conn = mysqli_connect('$host', '$user', '$passwd', 'mabase');


84.3. Les requêtes

84.3.1 SELECT


Exemple de requête SELECT et affichage des résultats dans un tableau :

// On créé la requête
$req = "SELECT * FROM table1";

// on envoie la requête
$res = mysql_query($req);

// on va scanner tous les tuples un par un
echo "<table>";
while ($data = mysql_fetch_assoc($res)) {
    // on affiche les résultats
    echo "<tr><td>".$data['id']."</td><td>".$data['texte']."</td></tr>";
}
echo "</table>";


Maintenant, avec MySQLi, cela donne :

// On créé la requête
$req = "SELECT * FROM table1";

// on envoie la requête
$res = $conn->query($req);

// on va scanner tous les tuples un par un
echo "<table>";
while ($data = mysqli_fetch_array($res)) {
    // on affiche les résultats
    echo "<tr><td>".$data['id']."</td><td>".$data['texte']."</td></tr>";
}
echo "</table>";


84.3.2 INSERT / DELETE

Avec MySQL, on utilisait :

// On créé la requête
$req = "INSERT INTO table1(texte) VALUES ('Du texte')";

// on envoie la requête
$res = mysql_query($req);


Avec MySQLI, cet exemple devient :

// On créé la requête
$req = "INSERT INTO table1(texte) VALUES ('Du texte mysqli')";

// on envoie la requête
$res = $conn->query($req);


84.4. Fermer la connexion

Avec MySQL, clore la connexion à la base :

// on ferme la connexion
mysql_close();


Et bien, avec MySQLi, cela devient :

// on ferme la connexion
mysqli_close($conn);


84.5. Se protéger des injections SQL

// Se protéger des injections SQL
$username = $conn->real_escape_string($_GET['username']);
$conn->query("SELECT * FROM users WHERE username = '$username'");


84.6. Requête préparée

Voici un exemple de requête à trous, préparée utilisant l'extension MySQLi:

// mysqli, Requête préparée
$query = $conn->prepare('SELECT * FROM users WHERE username = ?');
$query->bind_param('s', $username); // s = string, i = integer
$query->execute();


84.7. Tester le nombre de lignes

// on crée la requête SQL
$req = "SELECT * FROM table1 WHERE chk_actif=1;";

// on envoie la requête
$res = $conn->query($req) or die();

// Si on a des lignes...
if ( $res->num_rows > 0 ) {
    echo "On a des résultats";
} else {
    echo "On n'a aucun résultat";
}


85. Immutable vs Mutable


85.1. Email de prérequis

Voici l'email de prérequis.
https://marc.info/?l=php-internals&m=160935644205607&w=2

List: php-internals
Subject: =?UTF-8?Q?Re:_[PHP-DEV]_Analysis_of_property_visibility,_immutability,_a?= =?UTF-8?Q?nd_cloning_prop
From: "Larry Garfield" <larry () garfieldtech ! com>
Date: 2020-12-30 19:26:25
Message-ID: b3fba6e2-8547-4de6-a09f-bd980b24a97a () www ! fastmail ! com
[Download RAW message or body]


> > That's a good summary of why immutability and with-er methods (or some
> > equivalent) are more ergonomic.
> >
> > Another point to remember: Because of PHP's copy-on-write behavior, full on
> > immutability doesn't actually waste that much memory. It does use up some,
> > but far less than you think. (Again, based on the tests MWOP ran for PSR-7
> > a ways back.)
>
> I thought copy-on-write was only for arrays, not objects?
>
> Olle

Copy on write applies to all values; the caveat is that with objects, the value being \
copied is the handle that points to an object in memory, rather than the object \
itself. That means passing an object by reference can do some seriously unexpected \
things, which is why you basically never do so.

The point here is that if you have an object with 15 internal properties, it's memory \
usage is 15 zvals plus one zval for the object, plus one zval for the variable that \
points to it. (I'm over-simplifying here. A lot.) If you pass it to a function, \
only the one zval for the handle is duplicated, which is the same as for an integer.

If you clone the object, you don't duplicate 15+1 zvals. You duplicate just the one \
zval for the object itself, which reuses the existing 15 internal property entries. \
If in the new object you then update just the third one, PHP then duplicates just \
that one internal zval and modifies the new one. So you still are using only 18 \
zvals, not 36 zvals. (Engine people: Yes, I am *very* over-simplifying. I know.)

Basically, what in most languages would require manually implementing "immutable data \
structures" we get for free in PHP, which is seriously sweet.

The net result is that a with-er chain like this:

$foo2 = $foo->withBar('x')->withBaz('y')->withBeep('z');

is way, way less expensive than it looks, both on memory and CPU. It is more \
expensive than setters, but not by much.

That's why I don't think the distinction between unique and immutable mentioned \
up-thread is that big of a deal in PHP, specifically. Yes, they're different things, \
but the cost of them is not all that different because of CoW, so considering them \
separately is not as important as it would be in a language that doesn't \
automatically do CoW in the background for us.

(Whoever in the 90s decided to bake CoW into the engine, thank you. It's an \
incredibly nice foundational feature.)

--Larry Garfield

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php

85.2. Ce que j'en comprends

J'en comprends que si on construit des clones d'instances à chaque withXxx plutôt que de faire un clone suivi de setter, même si selon l'auteur, cela consomme un peu plus de mémoire/CPU mais c'est négligeable comparé à l'intéret que c'est de travailler avec des immutables.

En bref, comparer ceci :

<?php
$foo = new Foo();
$foo2 = $foo->withBar('x')->withBaz('y')->withBeep('z');

Avec ceci :

<?php
$foo = new Foo();
$foo2 = clone $foo;
$foo2 = $foo2->setBar('x')->setBaz('y')->setBeep('z');

Cette deuxième version, pour un même résultat, serait plus gourmande en mémoire et en CPU que la première version.

85.3. On teste !

85.3.1 Classe Foo.

Bon, ce qui est bien, c'est que l'on peut définir la même classe Foo pour les deux façons de faire.

<?php
class Foo
{
    protected string $bar = 'a';
    protected string $baz = 'b';
    protected string $beep = 'c';

    public function __construct()
    {
        return $this;
    }

    public function setBar(string $bar)
    {
        $this->bar = $bar;

        return $this;
    }

    public function setBaz(string $baz)
    {
        $this->baz = $baz;

        return $this;
    }

    public function setBeep(string $beep)
    {
        $this->beep = $beep;

        return $this;
    }

    public function withBar(string $bar)
    {
        $clonedFoo = clone $this;
        $clonedFoo->bar = $bar;

        return $clonedFoo;
    }
    public function withBaz(string $baz)
    {
        $clonedFoo = clone $this;
        $clonedFoo->baz = $baz;

        return $clonedFoo;
    }
    public function withBeep(string $beep)
    {
        $clonedFoo = clone $this;
        $clonedFoo->beep = $beep;

        return $clonedFoo;
    }
}


85.3.2 Test de l'usage mémoire.

Pour calculer l'usage qu'est faite de la mémoire, on peut utiliser la fonction PHP memory_get_usage à plusieurs endroits du code et faire la différence.

$m1 =  memory_get_usage();
$foo = new Foo();
$foo2 = $foo->withBar('x')->withBaz('y')->withBeep('z');
$m2 = memory_get_usage();
echo ($m2 - $m1).PHP_EOL;


$m1 =  memory_get_usage();
$foo3 = new Foo();
$foo4 = clone $foo3;
$foo4 = $foo4->setBar('x')->setBaz('y')->setBeep('z');
$m2 = memory_get_usage();
echo ($m2 - $m1).PHP_EOL;


Le résultat que j'ai obtenu est celui-ci :

192
192

Autrement dit : aucune différence. Ce qui amène plusieurs hypothèses :
- soit je n'ai pas compris ce dont il était question ;
- soit j'ai mal implémenté le truc ;
- soit memory_get_usage ne fait pas ce que je souhaite ;
- ou soit il n'y a effectivement aucune différence de mémoire, en tout cas au final, peut-être qu'à chaque étape intermédiaire, si ;

En testant la dernière hypothèse, et donc en regardant la mémoire utilisée maximale sur chaque cas entre chaque étape, j'obtiens :
Cas 1 :

<?php
$max1 = 0;
$max1 = max($max1, memory_get_usage());
$foo = new Foo();
$max1 = max($max1, memory_get_usage());
$foo2 = $foo->withBar('x');
$max1 = max($max1, memory_get_usage());
$foo2 = $foo2->withBaz('y');
$max1 = max($max1, memory_get_usage());
$foo2 = $foo2->withBeep('z');
$max1 = max($max1, memory_get_usage());
echo ($max1).PHP_EOL;

Cas 2 :

$max1 = 0;
$max1 = max($max1, memory_get_usage());
$foo3 = new Foo();
$max1 = max($max1, memory_get_usage());
$foo4 = clone $foo3;
$max1 = max($max1, memory_get_usage());
$foo4 = $foo4->setBar('x');
$max1 = max($max1, memory_get_usage());
$foo4 = $foo4->setBaz('y');
$max1 = max($max1, memory_get_usage());
$foo4 = $foo4->setBeep('z');
$max1 = max($max1, memory_get_usage());
echo ($max1).PHP_EOL;

Cas 1 : 406464
Cas 2 : 406488

Soit une différence de 14 octets, le deuxième cas étant donc à un moment plus gourmand en mémoire que le premier cas.

85.3.3 Autres benchmarks

$foo = new Foo();
$foo2 = $foo->withBar('x')->withBaz('y')->withBeep('z');

-> 404680

$foo = new Foo();
$foo2 = $foo->withBar('x');
$foo2 = $foo2->withBaz('y');
$foo2 = $foo2->withBeep('z');

-> 404808

$foo3 = new Foo();
$foo4 = clone $foo3;
$foo4 = $foo4->setBar('x');
$foo4 = $foo4->setBaz('y');
$foo4 = $foo4->setBeep('z');

-> 404832

$foo3 = new Foo();
$foo4 = clone $foo3;
$foo4 = $foo4->setBar('x')->setBaz('y')->setBeep('z');

-> 404832

Analyse : La version immutable où les withXxx sont chainés consomme moins de mémoire que les autres versions dont celle mutable avec les setXxx chainés, donc ce code-ci n'est pas une preuve de ce que dit l'auteur du mail, ce serait même l'inverse.

85.4. Conclusion

Bien que j'ai n'a pas réussi à montrer que la mémoire utilisée est plus grande lorsqu'on manipule des objets immutables, et c'est même l'inverse dans mon cas, la version avec des withXxx parait être très bien gérée par PHP et donc on peut imaginer que coder avec des objets immutables serait très souhaité.

86. Qui est le premier, l'oeuf ou la poule ?


Considérons deux classes, Hen et Egg avec ces deux règles :
* Lorsqu'une poule pond, elle crée un oeuf.
* Lorsqu'un oeuf éclot, une poule nait.

<?php
class Hen
{
    public function lay()
    {
        return new Egg();
    }
}

class Egg
{
    public function hatch()
    {
        return new Hen();
    }
}


On peut donc créer des petits enfants très simplement :

$hen = (new Hen())->lay()->hatch();


Ce design pattern est à ranger dans la catégorie des Constructors (ce n'est qu'un double FactoryMethod).

87. Quand le \n fait un u/ puis un \n


Un petit bout de code juste pour le fun. On va écrire la fonction br2nl, après tout il n'y a pas de raison qu'on puisse aller dans le sens retour à la ligne PHP-> retour à la ligne HTML et pas dans le sens inverse.

C'est parti donc, ! Attention cela risque d'être intense (note : je ne prends pas en compte le xHTML, faut pas déconner).

function br2nl(string $string)
{
    return str_replace('<br>', PHP_EOL, $string);
}

et vient alors naturellement les fonctions suivantes :

function saltoAvant(string $string) 
{
    return br2nl(nl2br($string));
}

function saltoArriere(string $string)
{
    return nl2br(br2nl($string));
}


On sait jamais si le \n change d'avis une fois sur le u/, cela peut être pratique !

88. Un Echo lointain franchit les âges


Un code artistique :

<?php
echo 'echo';

Le résultat est bien sûr :

echo


je trouve cela très beau.


89. Le lcwords


Qu'on résume :
* lcfirst, c'est pour mettre la première lettre d'une chaine en minuscule
* ucfirst, c'est pour mettre la première lettre d'une chaine en majuscule
* ucwords, c'est pour mettre la première lettre de chaque mot d'une chaine en majuscule

lc voulant dire lower case et uc voulant dire upper case.

Quant à words, cela veut dire 'mots', et bien sûr first, c'est pour dire que c'est la première lettre, encore une étrangeté sur le nommage des fonctions et la cohérence de tout ça en PHP (<8.0).
Mais admettons ! La question que je me pose est, "mais pourquoi donc lcfirst existe ?".

Après une rapide recherche sur Github, c'est édifiant :
* lcfirst a été surtout utilisé dans des tests unitaires...
* lcfirst a été utilisé pour générer du code php (ex: générer "$toto = new Toto()" -> on génère le nom de l'instance en fonction de celui de la classe, pareil c'est surement pour faire des tests mais plus surement utiles cette fois, ex2: générer un namespace en fonction du nom du fichier)

je n'ai à ce jour pas d'idée fonctionnelle de l'intérêt de cette fonction mais au moins elle a été utilisée.

Par contre, mais où est la fonction lcwords ?!! Qu'à cela ne tienne, en voici une implémentation :

function lcwords(string $string): string
{
    return implode(' ', array_map(function(string $piece) {
            return lcfirst($piece);
        }, explode(' ', $string)
    ));
}

echo lcwords('THE CAKE IS A LIE');


Le résultat est sans appel :

>tHE cAKE iS a lIE

Chaque première lettre des mots est bien en minuscule, super donc !

Evidemment le nom de la fonction est dans l'esprit du nommage des autres.

90. PHP RFC: Class Friendship

90.1. Présentation de la RFC

Date: 2017-09-21
Url de la RFC : https://wiki.php.net/rfc/friend-classes

Principe de base :
on introduit le mot clef "friend" qui permet à une classe d'avoir accès aux propriétés protected et privée d'une autre classe pour éventuellement séparer les règles d'affichages des paramètres de contenu.

Clairement, je comprend pourquoi cette RFC a été refusée en grande majorité par la communauté (6 votes POUR / 27 votes CONTRE).
Alors déjà que l'utilisation des Traits en PHP me semble être une grosse ruse pour palier à l'héritage multiple, alors ce principe de Friend me semble apporter plus de complexité dans le code que la souplesse que cela entend.
La goutte de trop est l'exemple du FibonacciTest : on commence à mettre une dépendance avec les classes de test DANS le code de l'application.
Techniquement, on sent bien que le dossier src/test ou équivalent ne doit pas partir en l'état en production, le code source qui tourne en production doit être le plus light possible et ne pas embarquer des choses qui ne s'y executeront jamais : d'une part c'est plus "écolo" car ça évite de transporter des milliers de fichiers pour rien sur les serveurs de production mais en plus si quelqu'un arrivait à executer des fichiers de test à distance, rien ne dit qu'il executerait pas un programme qui effacerait la base de données pour charger son jeu de test.

Donc non.

La réponse la plus satisfaisante pour lire des attributs publiques reste de les rendre accessibles par des getters soit généraux, soit spécifiques dans le cas où on souhaiterait qu'une et une seule classe puisse avoir accès aux données.

90.2. Exemple sans l'usage de friend via des getters classiques

Eventuellement, ça pourrait ressembler à ceci (note : j'ai enlevé la dépendance avec la classe Uuid, non pertinente) :

NoFriend.php
<?php

class Person
{
    protected $id;
    protected $firstName;
    protected $lastName;

    public function __construct($id, $firstName, $lastName)
    {
        $this->id = $id;
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getFirstName()
    {
        return $this->firstName;
    }

    public function getLastName()
    {
        return $this->lastName;
    }
}

class HumanResourceReport
{
    private $person;

    public function __construct(Person $person)
    {
        $this->person = $person;
    }

    public function getFullName()
    {
        return $this->person->getFirstName() . ' ' . $this->person->getLastName();
    }

    public function getReportIdentifier()
    {
        return "HR_REPORT_ID_{$this->person->getId()}";
    }
}

$person = new Person('uniq_id', 'Alice', 'Wonderland');
$report = new HumanResourceReport($person);

var_dump($report->getFullName()); // string(16) "Alice Wonderland"
var_dump($report->getReportIdentifier()); // string(49) "HR_REPORT_ID_uniq_id"

90.3. Exemple sans l'usage de friend via des getters spécifiques

Voici l'idée : les getters de la classe Person vérifient que la classe appellante est bien la seule à avoir le droit d'accéder aux données.
La difficulté ici est de savoir quelle est la classe appelante et en PHP, une (seule?) façon de faire est d'utiliser la fonction debug_backtrace qui permet de savoir le cheminement entre les différents appels de fonctions.

<?php
class Person
{
    protected $id;
    protected $firstName;
    protected $lastName;

    public function __construct($id, $firstName, $lastName)
    {
        $this->id = $id;
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }

    public function mustBeGranted()
    {
        $trace = debug_backtrace();

        if (isset($trace[2]['class']) && HumanResourceReport::class === $trace[2]['class']) {
            return true;
        }

        throw new \Exception("The caller class is not allowed to do this.");
    }

    public function getId()
    {
        $this->mustBeGranted();

        return $this->id;
    }

    public function getFirstName()
    {
        $this->mustBeGranted();

        return $this->firstName;
    }

    public function getLastName()
    {
        $this->mustBeGranted();

        return $this->lastName;
    }
}

class HumanResourceReport
{
    private $person;

    public function __construct(Person $person)
    {
        $this->person = $person;
    }

    public function getFullName()
    {
        return $this->person->getFirstName() . ' ' . $this->person->getLastName();
    }

    public function getReportIdentifier()
    {
        return "HR_REPORT_ID_{$this->person->getId()}";
    }
}

$person = new Person('uniq_id', 'Alice', 'Wonderland');
$report = new HumanResourceReport($person);

var_dump($report->getFullName()); // string(16) "Alice Wonderland"
var_dump($report->getReportIdentifier()); // string(49) "HR_REPORT_ID_uniq_id"
var_dump($person->getFirstName()); // PHP Fatal error:  Uncaught Exception: The caller class is not allowed to do this.

90.3.1 Pourquoi retourner une exception ?

1. Parce que c'est mieux que de faire un trigger_error puisqu'on peut le catcher de façon plus élégante.
2. Il faudrait définir une exception spécifique.

90.3.2 Pourquoi l'index 2 ?

Ici on utilise l'index 2 du debug_backtrace car dans le cas où on définirait une méthode intermédiaire mustBeGranted :
index 0 : on aurait class: Person, function: mustBeGranted
index 1 : on aurait class: Person, function: getId (ou l'une des deux autres)
index 2 : on aurait class: HumanResourceReport, function getFullName (ou getReportIdentifier)
Je ne recommande pas forcément ce genre de code pour deux raisons :
1. L'usage de debug_backtrace ne devrait jamais être utilisé ailleurs qu'en développement/test
2. La classe Person ne devrait pas avoir de dépendance avec la classe HumanResourceReport (c'est d'ailleurs aussi pour ça que je suis contre l'idée du 'trait' friend).


91. PHP RFC: Default constructors

91.1. Présentation de la RFC

Date: 2014-11-05
URL : https://wiki.php.net/rfc/default_ctor
Auteur: Stas Malyshev

Cette RFC a été refusée car elle n'a pas eu assez de votes POUR alors qu'en majorité absolue, elle aurait pu passer (27 POUR VS 20 CONTRE).

L'idée derrière cette RFC est assez simple, on permet à une classe fille d'utiliser même s'il n'existe pas le constructeur de la classe mère.

Selon l'auteur, on pourrait toujours faire un parent::__construct(); dans le constructeur de la classe fille sans trop se soucier de ce que fait exactement la classe mère. Cela éviterait aussi de devoir modifier toutes les classes filles si jamais leur classe mère avait d'un coup un constructeur.

Pourquoi ce n'est pas grave si cette RFC ne passe pas ?
Alors, il me semble tout à fait raisonnable de penser que le role du développeur est non seulement d'être curieux sur les classes dont hériteraient les siennes mais en plus d'être responsable en les utilisant de la bonne manière.

La classe mère est modifiée ? Qu'à cela ne tienne : oui il faut peut-être rééditer toutes les classes filles. Oui, cela peut tout à fait être rébarbatif, mais le seul domaine où le développeur ne doit pas être paresseux est justement celui de la programmation.
Créer un constructeur par défaut vide et laisser les développeurs l'utiliser dans leurs classes filles "au cas-où", non seulement ce n'est pas très rigoureux comme façon de faire, mais en plus, rien n'indique que le constructeur, s'il venait à être créé dans la classe mère n'aurait aucun paramètre, donc, il serait fort probable que dans beaucoup de cas, il faille de toute façon revenir sur le constructeur des classes filles.

91.2. Alors que faire pour pallier à ceci ?

Et bien, on peut tout à fait créer une classe intermédiaire dont étendrait les classes filles qui possèderaient un constructeur qui héritera si besoin du constructeur de la classe grand-mère. Cela ne permet pas de répondre à un constructeur qui aurait plusieurs paramètres, mais cela permet en tout cas de répondre au besoin de cette RFC.

91.2.1 Proposition d'implémentation

Nous avons ici quatre classes, la classe GrandPa, celle dont va hériter la classe Dad, elle-même dont héritent les classes Daughter et Son.
La classe Dad joue ici un rôle de tampon entre la classe GrandPa et les deux autres, on peut lui créer un constructeur vide "au cas-où" qui héritera si besoin du constructeur de la classe GrandPa si jamais celui-ci venait à être créé dans le futur.

<?php

class GrandPa
{
    
}

class Dad extends GrandPa
{
    public function __construct()
    {
        
    }
}

class Son extends Dad
{
    public function __construct()
    {
        parent::__construct();
    }
}

class Daughter extends Dad
{
    public function __construct()
    {
        parent::__construct();
    }
}

Ici encore, c'est une idée que je ne cautionne pas dans la mesure où c'est du code qui ne sert à rien au moment où il est fait, autant se retrousser les manches au bon moment plutôt que de prévoir d'innombrabres probables situations.

91.3. Conclusion

C'est plutôt une bonne chose que cette RFC ne soit pas validée car la création de constructeurs fictifs aurait surement créé plus d'effets de bord qu'autre chose, car si jamais la classe mère était mise à jour sans même que le développeur s'en aperçoive, c'est à ce moment là qu'il perdrait la maitrise de son code. Développer sur de l'implicite ne me semble pas être une bonne chose.

92. PHP RFC: Restrict $GLOBALS usage

Date : 2020-12-02
Auteur : Nikita Popov
URL : https://wiki.php.net/rfc/restrict_globals_usage

A creuser mais je ne vois pas l'intérêt de modifier quelque chose qui ne devrait pas être utilisé depuis un moment.

93. PHP RFC: Wall-Clock Time Based Execution Timeout

93.1. Introduction de la RFC

Lien de la RFC : https://wiki.php.net/rfc/max_execution_wall_time
Date d'introduction : 2020-12-12

Cette RFC propose d'introduire le paramètre INI max_execution_wall_time.
Ce paramètre définit un temps de time-out auquel le script PHP doit s'arreter.

Ce qui me semble existe déjà puisque ça porte d'ailleurs le nom de max_execution_time.
Sauf que l'auteur indique le max_execution_time utilise le temps CPU et non pas le "wall-clock time" (alias du real-time).

93.2. Wall-clock time VS CPU time

Tout se joue dans la définition de ces deux termes.
* Dans le cas du CPU Time, il faut que le script PHP fasse un vrai traitement (càd qu'à chaque ligne de code interprété, on compte à nouveau le temps qu'il reste).
* Dans le cas du Real Time, peu importe ce que le script PHP fasse, si le temps est écoulé, il s'arrête, même s'il faisait quelque chose d'important.

Autrement dit, on pourrait penser intuitivement que le max_execution_time correspond au cas de figure numéro 2, alors qu'il correspond au cas numéro 1.

L'auteur pointe donc un problème : si le script PHP est en attente de quelque chose, il continu d'être executé.
Et donc, il risque d'y avoir une accumulation d'execution de script PHP au niveau du serveur web, ce qui peut faire tomber un site web.

93.3. Démonstration

J'ai souhaité vérifier qu'il était possible de dépasser le temps de max_execution_time avec un appel HTTP.
Pour ce faire, j'ai créer un fichier index.php que j'interroge via mon navigateur, rien de très sorcier.

93.3.1 Cas où le script PHP s'arrète après un time out

index.php
<?php
$i = 0;
do {
    $i++;
} while ($i < 100000000000);

Si on considère que le paramètre max_execution_time est à la valeur 30, ce genre de code part en time out (le nombre d'itérations dépend de la puissance de votre CPU.
Ici le script fait un traitement, on peut se mettre à la place d'un outil comme xdebug et faire des boucles visuellement.
Pendant ce traitement les secondes qui défilent sont prises en compte.

Le script s'arrete au bout de 30 secondes, on peut en voir la trace dans le log du serveur web (ici Apache/2.4.41) :
[Tue Dec 15 15:04:05.217052 2020] [php7:error] [pid 1609] [client 127.0.0.1:43918] PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/html/index.php on line 8

93.3.2 Cas où le script PHP ne s'arrète pas

index.php
<?php
sleep(50);

Si on considère que le paramètre max_execution_time est à la valeur 30, ce genre de code continue de tourner après 30 secondes. La page s'affiche au bout de 50 secondes.
Ici c'est un sleep, on peut le changer, sauf que ce problème peut tout à fait arriver lorsque notre code PHP fait appel à un service externe (mysql/memcached/url). Et là c'est effectivement le début d'une belle pagaille car les appels vont se cumuler et le service d'en face va avoir deux options : soit il est très performant au démarrage et il traite tout ce petit monde, soit il décide de dire stop et tous les scripts PHP en cours d'execution font finalement s'arreter.

93.4. Pertinente de la RFC

93.4.1 Le plutôt pour

Alors est ce que cette RFC est pertinente ? Et bien, la réponse me semble oui car on aurait préféré que ce comportement fusse celui du max_execution_time.
Ce qui est ennuyeux, comme le soulève d'auteur, c'est qu'il serait extrèmement dangereux de modifier le comportement du max_execution_time car cela aurait des effets de bords potentiellement très graves chez l'ensemble des utilisateurs du PHP (80% des sites web).
Le plus prudent étant en effet de créer un nouveau paramètre appelé max_execution_wall_time.

93.4.2 Le plutôt contre

Le risque : cependant, il faut aussi se dire que si l'on force le script PHP à réellement s'arreter, cela veut dire qu'il ne pourra plus pallier à la faiblesse des services qu'il utilisera. Autrement dit, si le serveur MySql d'en face met 2 minutes à exécuter une requête non optimisée (cas classique : il manquerait un index), et bien le script PHP plantera alors que ce n'était pas en soi de sa faute.
Donc, avant d'introduire ce paramètre max_execution_wall_time, il faudrait modifier son code pour introduire des temps limites aux appels externes, et donc être capable de determiner pour tous les chemins de code possibles le temps maximum que l'on accorderait aux services externes, chose qui me semble en pratique infaisable.

En bref, si le max_execution_time a ce comportement là, ce n'est peut être pas pour rien. On verra ce qu'il en est dans les mois à venir de cette proposition de Máté Kocsis.

94. PHP et son futur

94.1. LA page à connaitre pour faire de la veille en PHP

Il existe une page que je ne connais que depuis novembre 2020 et cette page a changé ma vie (enfin non, mais bon, c'est pour faire genre) : https://wiki.php.net/rfc

Cette page est peut être LA page qui permet de faire la veille la plus pragmatique que l'on puisse faire sur le PHP (et uniquement le PHP, cela ne couvre donc pas les frameworks, les autres bibliothèques et les autres langages, bien que certaines RFC y fassent parfois référence).

La page est découpée en plusieurs sections :

la section "Under Discussion" : ici tout se débat, tout se discute, ce sont les RFCs imaginées la veille après une bière de trop et écrites tout de même noir sur blanc et envoyé par email à la communauté PHP. C'est une section très riche pour la créativité car on parie un peu sur la pertinence de la proposition. Lorsque la RFC a été bien réfléchie passe alors l'étape du vote pour indiquer si oui ou non cela vaut le coup de l'implémenter dans le langage.
Lorsque les développeurs se sont mis d'accord sur l’intérêt de la RFC, c'est le moment d'en faire un papier un peu plus sérieux dans la section "In Draft", c'est l'étape de la conception et du développement. Lorsque celui ci est terminé, les développeurs votent à nouveau.

Suite au vote sur la RFC et l'acceptation de celle-ci, la RFC atterrit alors dans la section "Accepted", très chaud à ce moment là pour intégrer la version suivante du langage s'il s'agit d'une modification du code (parfois ce sont des RFC qui ne ciblent pas forcément le PHP).

la section "Implemented" qui veut dire que non seulement la RFC a été acceptée par l'équipe de développement mais en plus qu'elle fait partie intégrante de la version du langage ou des futures décisions.
Ce qui est intéressant lorsque l'on est simple utilisateur du PHP, c'est d'assister à ces réflexions extrêmement riches pour l'esprit. Non seulement elles permettent de comprendre pourquoi l'équipe de développement a validé ou non une idée mais en plus permet un allèchement non négligeable sur les futures montées de version.

On se pose également des questions légitimes comme "ha tiens c'est vrai que ça, ça existe sur d'autres langages, c'est étonnant et même curieux que non seulement ce ne soit proposé qu'à peine aujourd'hui mais surtout que l'on a réussi à s'en passer pendant toutes ces années" ; car c'est cela qui est assez magique avec les langages de programmations (oserais-je ajouter "Turingué" histoire d'appuyer le pléonasme) : dès lors que les fondamentaux existent (boucle/conditions/quelques types comme char, bool et float/modification de l'espace mémoire), tout le reste n'est que fioritures et gâteries pour le développeur (on appelle ça du sucre syntaxique).
Car honnetement : si la visibilité n'existait pas, ni les classes d'ailleurs, ni mêmes les types bool, int et même float, on pourrait s'en sortir uniquement avec des for et des string.

A noter qu'une RFC ne concerne pas uniquement une modification du code de PHP mais peut également agir sur la façon même dont les débats sont menés (ex: cette RFC qui revient sur une règle de vote des RFCs (c'est presque de l'inception comme ces parenthèses !))

94.2. Si quelqu'un sait pourquoi...

Ce qui est dommage avec ce système de RFC, c'est qu'il ne me parait pas possible de connaitre les motivations des votants.
Ainsi, même si on a l'intuition qu'une RFC n'est pas une bonne idée, il n'est pas possible de savoir si notre intuition est celle partagée par la majorité des votants ou si l'on est complétement à côté de la plaque.

95. PHP 7.1 Le type void en déclaration de méthode

Alors même si on aurait tendance à dire que ça sert un peu à rien, c'est toujours plus jolie à regarder lorsqu'on visualise une interface.

Mise à part la beauté générale, les programmes de vérification syntaxique auront également moins de peine à verifier que la méthode comporte bien un type de retour.

Exemple :

<?php
function myFunction(): void
{}


95.1.1 A quand la déclaration d'un retour de fonction d'un tableau d'objets ?

Cependant, il y a quelque chose qui me semble manquer beaucoup plus génant, c'est le retour de type tableau d'instances comme ceci par exemple MyClass[], idem pour le nom des paramètres.

Voici un code qui illustre l'utilisation de genre de type de retour :

<?php
function myFunction(): MyClass[]
{
      return [
        (new MyClass())->setAttr(true),
        (new MyClass())->setAttr(false),
      ];
}

C'est ça que j'appelle le vrai typage fort, dans l'idéal il faudrait arreter de penser qu'il faut à tout pris des retours de type array ou mixed (comme c'est le cas en PHP 8.0...), ça créé toujours de l'ambiguité dans le code, et quand on conçoit du code avec des types ambigüs, on a souvent le droit à des heures de TMA derrière. En outre, dans le monde du travail, c'est rarement le même développeur qui concoit et qui débugue par la suite, donc plus le code est explicite, plus c'est simple à corriger.

96. PHP 7.3 Rien de très utile


96.1. PHP 7.3 JsonException


Cette version de PHP nous fait faire l'économie de ne plus avoir à tester si le retour de json_decode est false pour retourner une exception personnalisé de type JsonException.
On peut en effet désormais utiliser l'option JSON_THROW_ON_ERROR pour obtenir directement une \JsonException.

96.1.1 En PHP <7.3 :

<?php
namespace App\Service;

use App\Service\JsonParserInterface;
use App\Exception\JsonException;

class JsonParser implements JsonParserInterface
{
    public static function jsonDecode(string $string): string
    {
        $decodedJson = json_decode($string);

        if (false === $decodedJson) {
            throw new JsonException();
        }

        return $decodedJson;
    }
}

Et on l'appelait par exemple de cette façon-là :

<?php
try {
    $decodedJson = JsonParser::jsonDecode($jsonToDecode);
} catch (JsonException $e) {
    // Log, alert etc...
}


96.1.2 En PHP 7.3, sans utiliser de classe spécifique :

<?php
try {
    $decodedJson = json_decode('bad json', null, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
    // Log, alert etc...
}


On pourrait penser que grâce à la version 7.3 de PHP, on pourrait désormais supprimer le service JsonParser puisque l'exception est désormais renvoyée par json_decode mais étant donné qu'il faut tout de même ajouter l'option JSON_THROW_ON_ERROR, cela semble tout de même toujours plus judicieux de continuer à l'encapsuler afin d'éviter de retrouver des dizaines d'occurences à json_decode dans le code source de son application.

96.1.3 La classe JsonParser en PHP 7.3 :

On pourrait définir cette classe en utilisant l'annotation @throws afin d'indiquer que la méthode retourne une exception native \JsonException.

<?php
namespace App\Service;

use App\Service\JsonParserInterface;

class JsonParser implements JsonParserInterface
{
    /**
    * @throws \JsonException
    */
    public static function jsonDecode(string $string): string
    {
        return json_decode($string, null, 512, JSON_THROW_ON_ERROR);
    }
}

96.1.4 La classe JsonParser en PHP 8.0 :

On peut simplifier l'appel en passant par les attributs nommés, d'où l'intéret de ne le faire qu'une et une seule fois dans son code.

<?php
namespace App\Service;

use App\Service\JsonParserInterface;

class JsonParser implements JsonParserInterface
{
    /**
    * @throws \JsonException
    */
    public static function jsonDecode(string $string): string
    {
        return json_decode($string, flags: JSON_THROW_ON_ERROR);
    }
}


96.2. PHP 7.3 Les virgules en fin de fonctions

Toujours pratique lorsque l'on duplique une ligne courante au moment de l'édition d'une méthode, cela permet d'avoir une façon de coder un peu plus flexible.

C'était déjà bien chouette de pouvoir le faire dans les tableaux.
Par contre, dans le cas des fonctions qui ont peu de paramètres dont le nom du type est court, cela génère beaucoup de lignes.

Voici un exemple d'utilisation où cela fait beaucoup de lignes pour pas grand chose :

<?php
function sum(
    string $a,
    string $b,
) {
   return $a + $b;
}


Voici un exemple d'utilisation où cela est déjà un peu plus pertinent en PHP 8.0 (on voit dans le futur) :

<?php
class UserPDFDocumentConvertor
{
    public function __construct(
        protected UserInterface $userInterface,
        protected DocumentManager $documentManager,
        protected EntityManager $entityManager,
        protected UserToSimpleUserMapper $userToSimpleUserMapper,
        protected PDFConvertor $pdfConvertor,
    ) {}
}

Ici, il est beaucoup plus simple d'injecter des services à notre classe puisqu'avec les raccourcis clavier (Cltr+D par défaut dans PHP Storm 2020.3), il est facile d'ajouter un paramètre de plus.

97. PHP 8.0 Un café bien sucré !

97.1. Introduction

Il y a deux bonnes nouvelles à cette version de PHP, la première est que l'équipe de développement prouve qu'elle se bouge le cul et que la version 7 n'était pas juste un coup d'essai, la seconde est que la compilation JIT vend du rêve : on souhaite tous que sans rien faire à part un apt upgrade, notre code -même moche- s'execute beaucoup plus vite ; c'est bien arrivé avec PHP 7 !

Concernant les autres nouveautés, sans dire que la plupart ne sert pas à grand chose, d'autres sont potentiellement risquées à mettre en place.

Je reprends ici l'article que j'ai vu présentant certaines mises à jour de PHP 8.0, je vous invite à le lire avant cette page : https://hoa.ro/fr/blog/2020-11-26-php-8-0-is-out/

97.2. Les nouveautés une par une

97.2.1 Les arguments nommés

Alors que Java fait ce qu'on appelle du polymorphisme et permet de créer des méthodes du même nom qui traitent d'objets de classe ou de type differents, l'équipe derrière PHP a préféré pallier aux problèmes d'utilisation qu'engendre les arguments facultatifs en introduisant les arguments nommés, le projet était dans les clous depuis 2013, c'est dire la volonté de l'équipe de développer ce truc.

Cette nouvelle version permet, lors de l'appel d'une fonction, de dire quel argument prend quelle valeur, cela permet donc d'éviter de copier/coller les valeurs par défaut de tous les arguments qu'on ne souhaite pas utiliser pour ne modifier que le dernier (ou l'un des suivants), technique donc assez dangereuse puisque, si via une mise à jour du langage, la valeur du paramètre par défaut change, il faudra peut être repassé sur notre code.
Avec les arguments nommés, on doit donc désormais copier coller le nom du paramètre, la bonne affaire !

Autant donner mon avis : c'est continuer à patauger dans la boue que d'utiliser les arguments nommés à tire la rigaud pour pallier aux fonctions natives et historiques ayant trop d'arguments facultatifs.

Ce qui me semble plus judicieux : créer des services et manipuler ces fonctions PHP natives qui ont beaucoup de paramètres facultatifs en jouant sur la valeur des attributs de l'objet qui encapsulera la fonction.
Dit comme cela, c'est très lourd et évidemment si on souhaite utiliser une fonction PHP une seule fois dans une commande par exemple, il est peut être mieux d'utiliser les arguments nommés. Autrement dit, ce n'est qu'une question de mesure et de bon sens.
Il serait assez farfelu par ailleurs d'encapsuler toutes les fonctions de PHP dans des classes. Même si c'est ce que font déjà d'autres langages comme le JS (ex: Math) ou le Java (String).

Certaines fonctions comme la fonction htmlspecialchars mériteraient clairement d'être encapsulées pour éviter de retrouver cette fonction à des milliers d'endroits du code. Ex, la classe PHPEngine du composant Templating de Symfony.

Donc le bon sens pour deux cas de figure :
* vous souhaitez utiliser une fonction native de manière exceptionnelle (dans du code poubelle, dans du code one shoot, dans un algorithme très précis) : OK pour la fonction native et ses arguments nommés
* vous souhaitez utiliser une fonction native de manière fréquente : encapsulez-la dans un service !

97.2.2 Les attributs

Alors là, prudence de chez prudence. La configuration des routes est un très bon exemple, on pourrait le faire en xml, en yaml, en annotation, voilà qu'on peut le faire en attribut. C'est pas loin de la boite de pandore ce qu'ils nous ont pondu là. D'un point de vue personnel, je ne souhaite pas être le premier à me casser les dents là dessus.

Petit détail amusant : on se dit depuis des années qu'il faut arreter d'utiliser le @ qui permet de planquer les erreurs PHP, encore plus pertinent depuis qu'ils ont catchés toutes ces erreurs pour nous permettre de les récupérer sous la forme d'exception. Dire qu'on n'utilise pas le @ parce qu'il est déjà utilisé pour quelque chose de mauvais, c'est d'autant plus dommage. Il aurait fallu qu'ils nous disent "Dès PHP 8, il n'est plus possible d'utiliser le @ pour rendre silencieuse les erreurs". Ils auraient pu alors l'utiliser pour les annotations.

97.2.3 Les types d’union

Cela rejoint mon avis sur les arguments nommés, faire une méthode qui accepte du poulet/du cochon/la valeur 8 et enfin le "j'en sais rien" derrière le mixed, ce n'est vraiment pas ce que je trouve de plus élégant. Le typage fort OUI, à plus forte raison quand on souhaite contrôler ce qui rentre dans les méthodes de nos classes, si c'est pour accepter tout et n'importe quoi en valeur d'argument, autant effectivement ne rien mettre ou mettre du mixed.
Là encore, le polymorphisme me semblait une solution bien plus adaptée.

97.2.3.1 Comparaison polymorphisme VS Type d'union

Si le polymorphisme existait en PHP :

interface MyClass
{
    public function myMethod(int $arg);
    public function myMethod(string $arg);
}


Voilà ce que qu'on peut faire en PHP 8.0 :

interface MyClass
{
    public function myMethod(int|string $arg);
}


Il n'y a alors pas le choix, il faudra forcément distinguer les différents types dans l'implémentation (ou laisser la magie du composant ou de PHP faire le cast lui même, ce qui n'est pas terrible.).

97.2.3.2 Petite apparté pour un cas similaire

Même chose pour le try catch \Exception $e, quand on écrit ceci, c'est surtout de la paresse qui s'exprime et si l'excuse c'est qu'on ne sait pas ce que renvoie telle ou telle librairie comme exception, c'est peut être qu'on n'a pas eu la patience de regarder les cas d'erreurs en détail et donc au final, l'utilisateur de l'application verra un beau message lui indiquant qu'une erreur a eu lieu et que toute l'équipe est sur le pont pour la résoudre, ce qui est bien entendu un mensonge.

97.2.4 Déclaration des attributs dans le constructeur

Alors...oui mais avec parcimonie. Ici aussi c'est du sucre syntaxique, on gagne un peu de temps et on évite de copier coller le nom de l'attribut.
Mais, certains attributs ne sont pas forcément setté par le constructeur, donc cela veut dire qu'il y aura du code dans et en dehors du constructeur, ce qui demandera donc au développeur de regarder à la fois en tête de classe et dans la signature du constructeur, autant dire que pour le côté confort de lecture, on repassera.

97.2.5 Nouvelles fonctions str_*

Mieux vaut tard que jamais. Cela ajoute "peut-être" un gain de performance, à voir. A noter qu'on persiste dans le Snake Case, alors que la tendance est depuis quelques temps au Camel Case (je n'ai pas de préférence entre les deux, mais j'aimerais pour le plaisir éviter de mélanger les deux entre le langage natif et tel ou tel composant).

97.2.6 Nouvelle interface Stringable

Le _toString est quelque chose qui ne devrait être utilisé qu'en dev, tout est dit.

97.2.7 L’opérateur nullsafe

Je dois dire que ce sucre syntaxique là est plaisant mais à utiliser avec parcimonie car comme le @, c'est un coup à se retrouver avec des dizaines de faux '?' qu'un développeur aura mis pour résoudre des bugs alors que peut être que le bug réel est avéré : ex: si le client n'a pas d'adresse postal alors qu'il a passé sa commande et qu'il souhaite être livré, ce n'est pas trop la peine de corriger le bout de code qui fait un getAdress()->getCP() ; non, il faut :
1. mettre à jour la donnée pour renseigner son adresse
2. mettre à jour le code qui aurait dû la renseigner au moment de la commande, quite à ne pas confirmer la commande par ailleurs

97.2.8 Lever des exceptions dans les expressions

Je ne suis pas ultra fan des expressions inlines car elles sont moins lisibles et me semblent plus difficiles à débugguer, mais cela n'est pas génant à voir.

97.2.9 match, le cousin du switch

Encore du sucre, mais plutôt utile.
Dans la plupart des cas cependant, un switch peut aussi faire plusieurs affectations de valeurs, avec le match, on serait obligé de faire du copier/coller tandis qu'avec le switch, chaque test n'est fait qu'une fois.

Ex:

Avec un switch

<?php
$myLang = 'francais';

switch($myLang) {
    case 'francais':
    case 'marseillais':
        $myCountry = 'france';
        $myLocale = 'fr_FR';
        break;
    default:
    case 'anglais':
        $myCountry = 'england';
        $myLocale = 'en_EN';
        break;
}

Avec un match

<?php
$myCountry = match($myLang) {
    'francais' ,  'marseillais' => 'france',
    'anglais', default => 'england',
};

$myLocale = match($myLang) {
    'francais', 'marseillais'  => 'fr_FR',
    'anglais', default  => 'en_EN',
};

Ici, même si on réplique le test, surement négligeable au niveau du temps d'execution, mais si on ajoutait une langue, il faudrait modifier le code à deux endroits.

97.3. Conclusion

Cette version de PHP 8.0 démontre que le développement de PHP continue. On appréciera l'effort que font les développeurs pour simplifier l'utilisation du langage et le rendre plus mature. On regrettera cependant que certaines nouveautés reposent encore sur des bases pointées du doigt depuis longtemps par les développeurs comme très discutables (erreurs @, nombre d'arguments facultatifs, nommage, le type mixed).

98. Les magazines orientés PHP

Il ne s'agit pas à proprement parlé de magazines papier mais plutot de sites web qui indiquent de façon fréquente les nouveautés dans le monde du PHP.

Comme :
* https://blog.jetbrains.com/phpstorm/category/php-annotated-monthly/

La version PDF du numéro de décembre 2020 de PHP arch
* https://www.phparch.com/wp-content/uploads/2020/12/PHP8-Distilled-December-2020-phparch.pdf

99. A propos de ce site

Le contenu de ce site est écrit en fonction de mes connaissances et de mes avis.
Tant que je suis en vie, ceux-ci peuvent changer.

* Mes écrits ne sont qu'une trace d'une pensée maladroite à un instant t.
* Certaines idées peuvent être écrites avec les mauvais mots.
* Certains concepts me dépassent parfois.

Je vous remercie pour votre indulgence.