Types of tests

Unit Testing

Unit Testing: Analysing a small piece of code is known as Unit Testing. Each unit test targets a unit of code in isolation. Unit testing should be as simple as possible, and it should not be depended on another functions/classes.

Тестваме само отделните методи на даденият клас. При подаване на някакви тестови аргументи, и знаейки какъв резултат трябва да буде върнат, виждаме дали методът работи правилно.

Не се допуска взаимодействие с какъвто и да било storage (DB, files…) както и например HTTP заявки към други компютри. Ако например ни трябва работа с DB, то фейкваме самата връзка за та не се осъществи реално работа с DB.

Всеки тест е напълно независим от останалите.

Functional Testing

Тези тестове имат цел про да симулират потребителското взаимодействие с SUT като например посредством браузър.
„Отвори този URL, събмитни дадена форма, в отговора има ли даден текст…“

Integration Testing

Много близък до Unit test тип, с разликата че работим реално с DB или друг сторидж.

Acceptance Testing

Acceptance Testing: This is the last phase of the testing process. Here we check the behavior of whole application from users side. End users insert the data and check the output whether it meets the required specifications or not. They just check the flow, not the functionality.

Допускат се взаимодействие с storage (DB, files…) както и например HTTP заявки към други компютри. Тестваме цялостното поведение на SUT от позицията на външен поребител.

Литература:

https://www.valuebound.com/resources/blog/understanding-phpunit-and-how-to-write-unit-test-cases

Unit tests doubles

Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.

Stubs

Просто методите на stubs са хардкоднати да връщат винаги даден предефиниран резултат при различни сценарии. Дори не хвърлят и exceptions или каквото и да е от самата бизнес логика.

A stub provides predetermined responses to calls made during a test. For example, if testing a payment gateway, a stub can simulate both successful and failed transactions, ensuring your code responds appropriately.

Example: Your test class depends on a method Calculate() taking 5 minutes to complete. Rather than wait for 5 minutes you can replace its real implementation with stub that returns hard-coded values; taking only a small fraction of the time.

Or the network connection to twitter API is very slow, which make my test slow. I know it will return timelines, so I made a stub simulating HTTP twitter API, so that my test will run it very fast, and I can running the test even I’m offline.

Mocks

Very similar to Stub but interaction-based rather than state-based. This means you don’t expect from Mock to return some value, but to assume that specific order of method calls are made.
Example: You’re testing a user registration class. After calling Save, it should call SendConfirmationEmail.

Stubs and Mocks are actually sub types of Mock, both swap real implementation with test implementation, but for different, specific reasons.

Stubs don’t fail your tests, mock can.

Stub – for replacing a method with code that returns a specified result.

Mock – a stub with an assertion that the method gets called.

С mock например можеш да провериш дали при дадени сценарии тестваният метод ще хвърли или не exception, дали минава или фейлва например някаква валидация…
Със stub – не, там просто се връща нещо хардкоднато.

Example in JavaScript:

var Stub = {
method_a: function(param_a, param_b){
return 'This is an static result';
}
}

var Mock = {
calls: {
method_a: 0
}

method_a: function(param_a, param_b){
this.method_a++;
console.log('Mock.method_a its been called!');
}
}

Литература:

https://www.bairesdev.com/blog/stub-vs-mock

https://stackoverflow.com/questions/3459287/whats-the-difference-between-a-mock-stub

https://martinfowler.com/articles/mocksArentStubs.html

Comparing Objects in PHP

Или каква е разликата между comparison operator (==) и identity operator (===)

Comparison operator дава true, когато „Two object instances are equal if they have the same attributes and values (values are compared with ==), and are instances of the same class.
Демек, когато данновата им част (пропъртитата) са еднакви, и са различни обекти, но от един и същи клас. А както знем класът по принцип е един вид тип. И също знаем, че по принцип Comparison operator (двойното равно) не се интересува от типът на сравняваните променливи, ако са скаларни. Но очевидно, и ако са обекти.

Демек това ще върне true.

var_dump(123 == '123');

Но това ще върне false.

var_dump(123 === '123');

Identity operator от друга стана ще върне true само ако двата обекта са всъщност една и съща инстанция, демек, когато: When using the identity operator (===), object variables are identical if and only if they refer to the same instance of the same class.

Демек, едното – ако са различни обекти, но от един клас, и с еднакви properties, другото – когато са един и същ обект.

Литература:

https://www.php.net/manual/en/language.oop5.object-comparison.php

Some of the PHP 8.2 new features

Readonly classes

До сега можеше да декларираме отделни пропъртита на даден клас като readonly.
Вече може всички пропъртита да ги декларираме readonly ако декларираме целият клас readonly.

Вместо:

class MyClass
{
	public readonly string $myValue,
	public readonly int $myOtherValue
	public readonly string $myAnotherValue
	public readonly int $myYetAnotherValue
}

това:

readonly class MyClass
{
	public string $myValue,
	public int $myOtherValue
	public string $myAnotherValue
	public int $myYetAnotherValue
}

Важно е, че в такъв случай, когато целият клас е readonly, всичките му пропъртита трябва задължително да имат тип, дори и да е mixed.

Също и, че не може да се декларират като readonly – enum, interface и trait. Само класове.

Dynamic class properties

Вече не може да се използват клас пропъртита, които не са декларирани.

From PHP 8.2 onwards, dynamic properties are deprecated. Setting a value to an undeclared class property will emit a deprecation notice the first time the property is set.

However, from PHP 9.0 onwards, setting the same will throw an ErrorException error.

…if you want to stop these deprecation notices after upgrading to PHP 8.2, you can use PHP 8.2’s new #[AllowDynamicProperties] attribute to allow dynamic properties on classes.

new standalone types

Вече имаме типове като true, false и null, демек такива, които са по принцип values, a не типове, могат вече да се използват и като типове. Демек, функция например може да ти връща тип null, или тип false, или тип true.

Но да не забравяме, че true и false са union type на bool. Демек, не можем да декларираме параметър или тип на връщана стойност като например bool|false.
Both true and false types are essentially a union type of PHP’s bool type. To avoid redundancy, you cannot declare these three types together in a union type. Doing so will result in a compile-time fatal error.

Disjunctive Normal Form (DNF) Types

sensitive parametters

Има случаи, в които волно или неволно можем да логнем например пароли, в различни log файлове или подобни по предназначение системи.

За да избегнем това програмно, ако подобна информация е например клас пропърти или параметър на функция, можем да ги декларираме като т.н. SensitiveParameter с помощта на атрибутът [\SensitiveParameter] по следният начин.

function example(
    $ham,
    #[\SensitiveParameter] $eggs,
    $butter
) {
    throw new \Exception('Error');
}

example('ham', 'eggs', 'butter');

/*
Fatal error: Uncaught Exception: Error in test.php:8
Stack trace:
#0 test.php(11): test('ham', Object(SensitiveParameterValue), 'butter')
#1 {main}
thrown in test.php on line 8
*/

When you generate a backtrace, any parameter with the \SensitiveParameter attribute will be replaced with a \SensitiveParameterValue object, and its real value will never be stored in the trace. The SensitiveParameterValue object encapsulates the actual parameter value — if you need it for any reason.

Fetch enum Properties in const Expressions

https: // kinsta.com/blog/php-8-2/#fetch-enum-properties-in-const-expressions

Allow Constants in Traits

Вече и в traits може да се дефинират константи.

trait Foo {
    public const FLAG_1 = 1;
    protected const FLAG_2 = 2;
    ...
Deprecate Partially Supported Callables

Първо, какво значи „partially supported callables“?

These callables are termed „partially supported“ because you cannot interact with them directly via $callable(). You can only get to them with the call_user_func($callable) function.

From PHP 8.2 onwards, any attempts to invoke such callables — such as via call_user_func() or array_map() functions — will throw a deprecation warning.

_GET/_POST аргументите се пренабиват от дясно на ляво

Демек, следващият със същото име пренабива предишният от дясно на ляво.

localhost/i.php?aaa=111&bbb=222&aaa=333

array(2) {
    ["aaa"]=> string(3) "333"
    ["bbb"]=> string(3) "222"
}

За POST заявки – аналогично, следващият аргумент със същото име пренабива предишният.

Съвременни проблеми при бубулацията на PHP грешките

Да видим какво пише тук:

As with normal exceptions, these Error exceptions will bubble up until they reach the first matching catch block. If there are no matching blocks, then any default exception handler installed with set_exception_handler() will be called, and if there is no default exception handler, then the exception will be converted to a fatal error and will be handled like a traditional error.

An exception handler handles exceptions that were not caught before. It is the nature of an exception that it discontinues execution of your program – since it declares an exceptional situation in which the program cannot continue (except you catch it).

Ако такъв блок не бъде намерен, се надяваме, че поне глобално имаме зададен exception handler с помощта на set_exception_handler()

Ако и такъв хендлър нямаме зададен, ще имаме fatal error.

Демек, хвърлен exception трябва да бъде прихванат задължително!

Добър въпрос е ако вече и в set_exception_handler() възникне грешка, дали ще се изпълни хендлърът, зададен с set_error_handler()?

Да, ще се. Нека разгледаме следният пример:

set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline,): bool {
    echo "Error has occurred: ", $errstr, PHP_EOL;

    /**
     * It is important to remember that the standard PHP error handler is completely bypassed
     * for the error types specified by error_levels UNLESS the callback function returns false.
     * Демек, ако върнем FALSE за даденият тип грешка, това ще значи,
     * че обработването и (на този тип грешка)
     * ще продължи И КЪМ стандартният обработчик.
     * Демек, върнем ли TRUE - до там! Няма да използваме и стандартният обработчик.
     * Но пак да кажем - до там само за даденият тип грешка.
     */
    return false;
});

set_exception_handler(function (Throwable $ex): void {
    echo "Uncaught exception: ", $eXd->getMessage(), PHP_EOL;
});

throw new Exception('Catch me if you can.');

echo "Not Executed" . PHP_EOL;

Имаме ли грешка в set_exception_handler(), демек по време на хвърляне на ексепшън, ще влезем и в set_error_handler()

Class constants can override each other

class Parend
{
    public const JJJ = 123;
}

class Chilt extends Parend
{
    public const JJJ = 456;

    static public function getJjj(): void
    {
        var_dump(self::JJJ);
    }
}

Chilt:: getJjj();    // int(456)

Е, ако родителската констата е private, резултатът пак ще е 456 но не в следствие на overrid-ване, а защото Child::JJJ ще се смята за напълно отделна константа, предвид видимостта и.

Статични и динамични callbacks

Първо нека си припомним какво е callback function.
Callback function is any reference to executable code that is passed as an argument to another piece of code.
Демек, код който се предава на друг код, под формата на аргумент, за да бъде изпълнен.

А относно статични и динамични callbacks, имаме две положения:

DYNAMIC Anonymous Functions

Когато имаме анонимна функция В КЛАС, това значи че $this променливата e ДОСТЪПНА в тази анонимна функция, демек става част от скоупа му, защото „the default behavior is that the class is automatically bound to the closure“.

Eто следният пример:

<?php

class Foo
{
    public function bar(): \Closure
    {
        return function (): void {
            var_dump($this);
        };
    }
}

$foo = new Foo();
$foo->bar()();

Първо, кой е callback-ът в даденият пример? Това е методът bar(). Защо? Защото какво връща той? Връща код, не конкретна стойност. Който код, може да бъде предаден като аргумент на друг код.

Второ, var_dump()-ът ще дъмпне самият обект – object(Foo)#1 (0) {}

Което очевидно значи, че щом даденият callback е динамичен то за всеки обект от класа Foo, ще имаме достъп до обектната променлива ($this), указваща към този даден обект, в който сме към даденият момент.

Static Anonymous Functions

Нека разгледаме горният код, но с една малка разлика – callback функцията нека е статична.

<?php

class Foo
{
    public function bar(): \Closure
    {
        return static function (): void {
            var_dump($this);
        };
    }
}

$foo = new Foo();
$foo->bar()();

Ще имаме Fatal error: Uncaught Error: Using $this when not in object context…

Вече, ако имаме статично пропърти на класа или статичен метод, можем да го достъпим, но чрез self

<?php
class Foo
{
    private static string $a = 'Hello world';

    public static function bar(): \Closure
    {
        return static function (): void {
            var_dump(self::$a);
        };
    }
}

$foo = new Foo();
$foo->bar()();      // Hello world

Литература:

https://en.wikipedia.org/wiki/Callback_(computer_programming)

https://www.designcise.com/web/tutorial/what-are-static-anonymous-functions-in-php

Iterator

<?php

interface Iterator extends Traversable
{
    public function current(): mixed;
    public function key(): mixed;
    public function next(): void;
    public function rewind(): void;
    public function valid(): bool;
}

Вграден интерфейс, който може да направи цикленето с foreach по-гъвкаво и къстъмизирано. Демек, да може да се задават определени действия за всяка итерация, задължавайки итериращият клас да имплементира тези действия.

Демек, ако имаме например обект, който имплементира този интерфейс, и го изциклим с foreach, при всяка итерация, в определен ред, ще бъдат извикани гореспоменатите методи.

Нека разгледаме следният пример:

<?php

class MyIterator implements IteratorInterface
{
    private int $position = 0;

    private array $array = array(
        "firstelement",
        "secondelement",
        "lastelement",
    );

    public function __construct()
    {
        $this->position = 0;
    }

    public function rewind(): void
    {
        var_dump(__METHOD__);
        $this->position = 0;
    }

    public function current(): mixed
    {
        var_dump(__METHOD__);
        return $this->array[$this->position];
    }

    public function key(): mixed
    {
        var_dump(__METHOD__);
        return $this->position;
    }

    public function next(): void
    {
        var_dump(__METHOD__);
        ++$this->position;
    }

    public function valid(): bool
    {
        var_dump(__METHOD__);
        return isset($this->array[$this->position]);
    }
}

$it = new myIterator;

foreach ($it as $key => $value) {
    var_dump($key, $value);
    echo "\n\n";
}

Имаме обект, имплементиращ въпросният интерфейс, и го циклим.

string(18) "myIterator::rewind"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(0)
string(12) "firstelement"


string(16) "myIterator::next"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(1)
string(13) "secondelement"


string(16) "myIterator::next"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(2)
string(11) "lastelement"


string(16) "myIterator::next"
string(17) "myIterator::valid"

Вижда се ясно в какъв ред автоматично се викат отделните методи.

Просто казано, идеята е да можем по свой начин, чрез всеки метод да зададем какво да се случва при всяка итерация.

Литература:

https://www.php.net/manual/en/class.iterator.php

__call сработва не само по име, но и по видимост

<?php
class Jazz
{
    public function __call(string $method, array $args): int
    {
        echo 'Majic ';
        return array_sum($args) * 2;
    }

    private function multiply(int $arg): int
    {
        echo 'No majic ';
        return $arg * 2;
    }
}

$class = new Jazz;
echo $class->multiply(3);      // Majic 6
echo PHP_EOL;
echo $class->multiply(15, 5);  // Majic 40

variables_order and request_order

„variables_order“ PHP директивата задава на кои суперглобални масиви да бъдат зададени стойности. Задаваме кои от GET, POST, COOKIE, ENV и SERVER ще презапишат една друга.

This directive determines which super global arrays are registered when PHP
starts up. G,P,C,E & S are abbreviations for the following respective super globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty paid for the registration of these arrays and because ENV is not as commonly used as the others, ENV is not recommended on productions servers. You can still get access to the environment variables through getenv().

Демек, използването на „E“ не се препоръчва, защото натоварва сървъра с още един масив, което е безсмислено, при условие че стойностите му могат така или иначе да се вземат с getenv()

А ако искаме да зададем РЕДЪТ НА ПРЕЗАПИСВАНЕ, това става с директивата „request_order“, която задава въпросният ред на презаписване.

Ето един пример:
Ако имаме request_order = „GP“, това значи, че ако имаме GET и POST променливи с еднакви имена, то стойността на дадената POST променлива ще презапише тази на съответната GET променлива.

Демек, редът на презаписване е – десните презаписват левите.

Какво, ако не зададем стойност на тази директива – „request_order“?
Leaving this value empty will cause PHP to use the value set in the variables_order directive. It does not mean it will leave the super globals array REQUEST empty.
Демек, ще използва стойността на директивата „variables_order“ и пак ще имаме $REQUEST масив със стойности.

Друго, което се вижда, че информацията в ENV e аналогична с тази, получена от getenv().

А разликата между ENV и SERVER e, че първото съдържа информация по-скоро за операционната система, както и такива, зададени от командният ред, такива, зададени с putenv()

А SERVER- променливи на ниво конкретен уебсърв, apache, nginx…

Литература:

https://www.php.net/manual/en/reserved.variables.request.php

https://stackoverflow.com/questions/43157933/what-is-the-request-precedence

PSR-11: Container interface

This document describes a common interface for dependency injection containers – тоест, когато имаме клас(ове), които ще използваме като data containers, той/те трябва да имплементират Psr\Container\ContainerInterface и задължително да имат поне метод get() и has().

The goal set by ContainerInterface is to standardize how frameworks and libraries make use of a container to obtain objects and parameters (called entries in the rest of this document).

The Psr\Container\ContainerInterface exposes two methods: get() and has()

Exceptions directly thrown by the container SHOULD implement the Psr\Container\ContainerExceptionInterface.

A call to the get method with a non-existing id MUST throw a Psr\Container\NotFoundExceptionInterface

Всеки елемент, съдържащ се в даденият контейнер, трябва да има уникален „oppaque string“, който уникално да го индентифицира. По този opaque string можем да изпоплзваме get(string) за да извлечем даденият елемент, или has(string) за да проверим дали такъв елемент съществува.

Специално за get() дали винаги по дадедн ID да връща един и същ резултат или не. Няма категоричност. Може и така и така.
Two successive calls to get with the same identifier SHOULD return the same value. However, depending on the implementor design and/or user configuration, different values might be returned, so user SHOULD NOT rely on getting the same value on 2 successive calls.

Литератра:

https://www.php-fig.org/psr/psr-11/

PSR-7: HTTP message interfaces

Относно методите, със get… вземаш, със with… променяш но първо клонираш.

Всичко в WWW се базира на клиент-сървър архитектурата и съответно – размяната на HTTP messages между тях – клиентът и сървът.

Относно натрапчивият въпрос, ОК какво разбираме под Request, или по-скоро – от коя страна да го гледаме? Tова, което клиентът изпраща, още от негова страна? Или това, което сървът е приел от негова страна?

Съдейки по този цитат:
Web browsers and HTTP clients such as cURL create HTTP request messages that are sent to a web server, which provides an HTTP response message.
Излиза, че е едно и също, и Рекуестът, и Респонсът, независимо от коя страна го гледаме. Нали все пак това, което изпраща клиентът е това, което получава сървът? Какво друго да е?

Прехвърлянето не променя нищо.

Рекуестът е рекуест и за рекуестващият, и за рекуестнатият, един и същ е, като волейболна топка.

Репонсът е респонс и за респондващият, и за респонднатият, пак на принципа на волейболната топка, едно и също е и за двете страни.

Демек, това което изпраща клиентът и съответно получава сървът, и за двете страни е един и същ Request.

И съответно и за Respons-ът.

Каквото подадеш е рекуест и за двете страни, каквото отговориш е респонс и за двете страни.

Но ако гледаме на дадените рекуести и респонси само от гледна точка на HTTP протоколът.

RequestInterface provides the general representation of an HTTP request message. However, server-side requests need additional treatment, due to the nature of the server-side environment.

Демек, когато сървът получи HTTP рекуест (RequestInterface), той от своя страна създава нов обект от ServerRequestInterface според своите си настройки и environment…
Това вече е ServerRequestInterface рекуест обект.

Демек, едното е така да се каже чистият HTTP рекуест, другото – нещо като разширената му версия според сърва, за да може да работим с него, с HTTP рекуеста. В какъв смисъл „разширената му версия“? В смисъл такъв, че например данните от HTTP рекуеста са парсирани и така да се каже, разпределени по масиви като $_GET, $_POST, $_COOKIE…

Messages Immutability

Messages are considered immutable; all methods that might change state MUST be implemented such that they retain the internal state of the current message and return an instance that contains the changed state.
Демек, нищо не променяш от това, което са ти подали, пазиш го непромененo. От него клонираш нов обект, в него променяш, и него използваш. Оригиналният запазваш напокътнат. На клонираният може всичко да променяш, но оригиналният трябва да си остане напокътнат.

PSR-7 препоръчва как едно PHP приложение, чисто откъм програмна страна, да приема HTTP заявките от клиента, и да създава HTTP отговорите, като задава интерфейси, които задават цялостна структура на класовете и методите, вършещи това.

This specification defines interfaces for the HTTP messages (request and response)  Psr\Http\Message\RequestInterface and Psr\Http\Message\ResponseInterface  respectively. Това сa двата интерфейса, които класовете за приемане на HTTP заявките и изпращане на HTTP отговорите, трябва да имплементират.

И двата наследяват Psr\Http\Message\MessageInterface

Но една такава заявка (Request) съдържа и много информация, извън самият HTTP протокол, информация свързана със самият сървър, демек това, което в PHP е в суперглобалният масив $_SERVER.
Затова трябва да имаме интерфейс Psr\Http\Message\ServerRequestInterface, наследяващ RequestInterface и работещ с информация от HTTP заявката, като:

  • The values represented in $_SERVER
  • Any cookies provided (generally via $_COOKIE)
  • Query string arguments (generally via $_GET, or as parsed via parse_str())
  • Upload files, if any (as represented by $_FILES)
  • Deserialized body parameters (generally from $_POST)
Concerning headers

Headers are retrieved by name from classes implementing the MessageInterface in a case-insensitive manner. For example, retrieving the foo header will return the same result as retrieving the FoO header. Similarly, setting the Foo header will overwrite any previously set foo header value.

Методите за извличане и сетване на хедъри(те) на дадено HTTP съобщение се задават в Psr\Http\Message\MessageInterface

Интересно е, че при извличане/променяне на хедър (което става по име), името му е case insensitive, демек няма разлика между FoO и fOO що се отнася до името на даденият хедър.

Streams

Не винаги е безопасно цялото боди на рекуеста да се запазва в променлива, защото може да бъде огромен и това да претовари сърва като му изчерпи паметта. Или просто да надвиши настройки на PHP като upload_max_filesize или post_max_size.

Without streams, opening a 20MB file will consume 20MB of memory.

Също, относно immutability. Няма такава за streams.
Защо?
Защото за да работиш със стрийм по принцип, трябва да можеш например динамично да задаваш поинтъра, където си в момента…

Стриймовете не са като обикновеният рекуест, който го получаваш an block, на цяло, на веднъж. Те са нещо динамично, пристигащо на части, така да се каже. Няма как от началото още да знаеш какво ще съдържа като цяло.

…immutability is impossible to enforce, as any code that interacts with the resource can potentially change its state (including cursor position, contents, and more)...
Демек, самото взаимодействие със стрийма може да го промени.

Достъпът до стриймовете могат да са read only, write only, randomly accessed

Server request

Сами по себе си RequestInterface обектите съдържат информация само и чисто за самият HTTP рекуест. Освен RequestInterface обект, имаме и ServerRequestInterface обект, който добавя към гореспоменатият обект още информация, свързана с конкретният сърв.

Литература:

https://www.php-fig.org/psr/psr-7/

https://www.php-fig.org/psr/psr-7/meta/#71-validation-of-header-names-and-values

PSR-4: Autoloader

Този PSR задава препоръки за файлструктурата на приложението, имената на клас-файловете и на самите класове, интерфейси и трейтове, с оглед на това да може стандартно да се аутолоудват.

  1. Трябва стандартът да е
    \<NamespaceName>\*\<ClassName>
  2. Tрябва за започва с vendor name – The fully qualified class name MUST have a top-level namespace name, also known as a „vendor namespace“.
  3. The fully qualified class name MAY have one or more sub-namespace names.
  4. Tрябва да завършва с името на класа/интерфейса/трейта (т.н. terminating class name)
  5. Подчертавки може да има но те нямат никакво смислово значение

Литература:

https://www.php-fig.org/psr/psr-4/

PSR-3: Logger Interface

The word implementor in this document is to be interpreted as someone implementing the LoggerInterface

Става дума за това какъв интерфейс трябва да имат logging библиотеките на едно PHP приложение. Kоито библиотеки трябва да получават обект от интерфейс Psr\Log\LoggerInterface, който разбира се може да бъде имплементиран според конкретните нужди.

LoggerInterface интерфейсът предлага 8 метода за запазване на лог информацията според нивата на грешките – debug, info, notice, warning, error, critical, alert, emergency (според rfc5424).

Има и девети метод log, който приема като първи аргумент – някое от горните нива като стринг, и извикването му трябва да дава същият резултат като съответният от горните методи. Подаването на аргумент, който не е от някой от горние нива, трябва да хвърля Psr\Log\InvalidArgumentException.
log() влиза в този интерфейс (LoggerInterface) но е един вид, изкуствено добавен метод, невлизащ в rfc5424, чиято цел е другите 8 метода да го извикват и реално – там да се извършва самото логване (The other eight methods are forwarding the message and context to it).

Относно съобщенията за грешките:

Every method accepts a string as the message, or an object with  __toString() method. Implementors MAY have special handling for the passed objects. If that is not the case, implementors MUST cast it to a string.

The message MAY contain placeholders which implementors MAY replace with values from the context array.

Литература:

https://www.php-fig.org/psr/psr-3/

https://datatracker.ietf.org/doc/html/rfc5424

PSR-12: Extended Coding Style

  • This specification extends, expands and replaces PSR-2, the coding style guide and requires adherence to PSR-1, the basic coding standard.
  • All PHP files MUST use the Unix LF (linefeed) line ending only.
  • All PHP files MUST end with a non-blank line, terminated with a single LF.
  • The closing ?> tag MUST be omitted from files containing only PHP.
  • There MUST NOT be a hard limit on line length.
  • The soft limit on line length MUST be 120 characters.
  • Lines SHOULD NOT be longer than 80 characters; lines longer than that SHOULD be split into multiple subsequent lines of no more than 80 characters each.
  • There MUST NOT be trailing whitespace at the end of lines.
  • Blank lines MAY be added to improve readability and to indicate related blocks of code except where explicitly forbidden.
  • There MUST NOT be more than one statement per line.
  • Code MUST use an indent of 4 spaces for each indent level, and MUST NOT use tabs for indenting.
  • All PHP reserved keywords and types MUST be in lower case.
  • Any new types and keywords added to future PHP versions MUST be in lower case.
  • Short form of type keywords MUST be used i.e. bool instead of booleanint instead of integer etc.

Всеки PHP файл трябва да следва следната стуктура:

  • започва с <?php
  • File-level docblock
  • One or more declare statements като например declare(strict_types=1);
  • Неймспесът на файла
  • use стейтмънти в следният ред – class-based use,  function-based use, constant-based use, разделени с празен ред за прегледност
  • самият код на файла
  • <?php тагът трябва да е на свой отделен ред само когато е първият такъв за файла, но не и ако например имаме вграден HTML код в PHP кода
  • import/require трябва да съдържат винаги само fully qualified пътища
  • Compound namespaces трябва да имат дълбочина, максимално 2
  • Като инстанцираме клас, на същият ред не трябва да имаме коментари. Т.е. след new Foo(); // не може!
  • The extends and implements keywords MUST be declared on the same line as the class name
  • The opening brace for the class MUST go on its own line and MUST NOT be preceded or followed by a blank line;
    the closing brace for the class MUST go on the next line after the body and MUST NOT be preceded by a blank line.
  • Ако даден клас имплементира повече от 1 интерфейс, те могат (не задължително) да са на отделни редове и да са индентирани веднъж.
  • use за трейтове трябва да е на следващият ред след отварящата скоба на класа. Ако са повече от един трейт – всеки един на отделен ред и със свое use.
  • Visibility MUST be declared on all properties, constants and methods.
  • There MUST NOT be more than one property declared per statement.
  • Property and method names MUST NOT be prefixed with a single underscore to indicate protected or private visibility. That is, an underscore prefix explicitly has no meaning.
  • abstract and final declarations MUST precede the visibility declaration.
  • static declaration MUST come after the visibility declaration.
  • след името на метода или функцията не трябва да има интервал(и), както и в скобите с параметрите след и преди отварящата и затварящата скоба. Както и трябва да има по един интервал между параметрите.
    public function fooBarBaz($arg1, &$arg2, $arg3 = [])
  • същото важи и при викане на функция или метод – bar();, а не bar ();
  • параметрите на метод или функция могат да са на отделен ред всеки, като
    ) и { след тях трябва да са на един ред.
    Както и типът на връщаният резултат, напр. ): string {
  • There MUST NOT be a space between the variadic three dot operator and the argument name – public function process(string $algorithm, …$parts)
  • при викане на метод или функция, аргументите могат да са на отделен ред като първият аргумент също трябва да е на нов ред.
  • Closures MUST be declared with a space after the function keyword, and a space before and after the use keyword.
  • и т.н…

Литература:

https://www.php-fig.org/psr/psr-12/

PSR-1: Basic Coding Standard

  • PHP code MUST use the long <?php ?> tags or the short-echo <?= ?> tags; it MUST NOT use the other tag variations.
  • Files MUST use only UTF-8 without BOM for PHP code.
  • PHP файл трябва да съдържа само декларация на клас (един клас, не повече), декларация на функции, декларации на константи или инстанцирането/извикването им но не и двете.
    This means each class is in a file by itself, and is in a namespace of at least one level: a top-level vendor name.
  • Namespaces and classes MUST follow an „autoloading“ PSR: [PSR-0PSR-4]. Важи също и за абскласове, интерфейси и трейтове.
    Всеки клас трябва да има поне вендор, който да му е първото ниво, демек, не трябва да има „хвърчащи“ класове. …and is in a namespace of at least one level: a top-level vendor name.
    A fully qualified class name has the following form:
\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
  • Class names MUST be declared in StudlyCaps (aka PascalCase).
  • Class constants MUST be declared in all upper case with underscore separators.
  • Method names MUST be declared in camelCase.
  • За имената на пропъртитата няма зададен стандарт, освен да се приеме един такъв и всички да са по него.

Литература:

https://www.php-fig.org/psr/psr-1/

String interning

Софтуерна техника, при която повтарящи се стрингове, които не се променят и са еднакви, например конфиг стрингове, не се съхраняват независимо един от друг, а се съхранява само един, и на всички места, където се използва, го достъпваме с указател.

Така се получава хеш структура, наречена string intern pool.

Освен че се пести памет, string interning също и ускорява сравняването на стрингове, защото вместо да се сравняват символ по символ, просто се сравняват дали указателите им са еднакви (a pointer equality test).

В PHP string interning e реализирано от OPcache библиотеката като имаме настройки като „opcache.interned_string_buffer„.

opcache.interned_strings_buffer – A pretty neat setting with like 0 documentation. PHP uses a technique called string interning to improve performance— so, for example, if you have the string „foobar“ 1000 times in your code, internally PHP will store 1 immutable variable for this string and just use a pointer to it for the other 999 times you use it. Cool. This setting takes it to the next level— instead of having a pool of these immutable string for each SINGLE php-fpm process, this setting shares it across ALL of your php-fpm processes. It saves memory and improves performance, especially in big applications. The value is set in megabytes, so set it to „16“ for 16MB. The default is low, 4MB.

PHP nested namespaces and scopes

Q. Променлива от даден неймспейс важи ли в под-неймспейсите, и под-под-неймспейсите…. и т.н… по принципа на матрьошката?
A. PHP does not allow nested namespacecs.

Ако изпълним следният код…

<?php
namespace MilkyWay;

$a = 'Mlechen Pyt';
const A = 'Mlechen Pyt';


namespace MilkyWay\SolarSystem;
$a = 'Slyncheva Sistema';
const A = 'Slyncheva Sistema';


namespace MilkyWay\SolarSystem\Earth;
$a = 'Zemq';
const A = 'Zemq';


namespace LargeMagellanicCloud;
$a = 'Large Magellanic Cloud';
const A = 'Large Magellanic Cloud';


var_dump(\MilkyWay\A);
var_dump($a);

var_dump(\MilkyWay\SolarSystem\A);
var_dump($a);

var_dump(\MilkyWay\SolarSystem\Earth\A);
var_dump($a);

var_dump(\LargeMagellanicCloud\A);
var_dump($a);

ще получим следните резултати:

string(11) "Mlechen Pyt"
string(22) "Large Magellanic Cloud"

string(17) "Slyncheva Sistema"
string(22) "Large Magellanic Cloud"

string(4) "Zemq"
string(22) "Large Magellanic Cloud"

string(22) "Large Magellanic Cloud"
string(22) "Large Magellanic Cloud"

от които се вижда, че неймспейсите не играят роля за задаване на скоуповете на константи, промеливи и т.н…, а целта и идеята им е изцяло за разграничаване на едноименни константи, променливи и т.н…

Тоест, те имат цел само за избягване на именни колизии и нямат нищо общо с капсулирането в области на видимост (scopes).

Променливите нямат нищо общо с неймспейсите. Например нашата променлива $a не се влияе от това в кой неймспейс е дефинирана. Не можем да имаме нещо от рода на \MilkyWay\$a. Неймспесите са само за класове, константи и функции.

Идеята на неймспейсите е само да гарантират уникалността на имената на класовете, константите и променливите.

Неймспейсите се използват само при класовете и функциите.

Note: Fully qualified names (i.e. names starting with a backslash) are not allowed in namespace declarations, because such constructs are interpreted as relative namespace expressions.
Демек, не може да декларираме неймспейс със namespace \SomeName…;

От примерният код също виждаме, че в един файл можем да имаме повече от един неймспейс, но това е силно непрепоръчително, въпреки че самият език PHP го позволява.

PHP 8 new features

Union types

Union types are a collection of two or more types which indicate that either one of those can be used.

Note that void can never be part of a union type, since it indicates „no return value at all“.

Furthermore, nullable unions can be written using |null, or by using the existing ? notation.

public function foo(Foo|Bar $input): int|float;
public function foo(Foo|null $foo): void;
public function bar(?Bar $bar): void;

The nullsafe operator

Вече може да се взема първият not null резултат от повече функции.

$dateAsString = $booking->getStartDate()?->asDateTimeString();

Тук $dateAsString ще приеме стойността на втората функция, ако първата е null. Toест, все едно null coalescing operator но за функции.

Named arguments

Удобството е, че не само няма нужда да се спазва реда на аргументите, но и не е задължително всички да бъдат подавани (за такива трябва разбира се, да имаме по подразбиране null)

function foo(string $a, string $b, ?string $c = null, ?string $d = null)
{
    /* ... */
}

foo( b: 'value b', a: 'value a', d: 'value d', );

Attributes

https://lindevs.com/using-attributes-in-php-8-0

https://dev.to/icolomina/using-php-attributes-easily-2fpo

https://www.amitmerchant.com/how-to-use-php-80-attributes/

Match expression

Подобрение на добрият, стар switch оператор. Реално е syntactic sugar, но има и някои предимства:
1) може да връща резултат, който да се присвои на променлива;
2) използва стриктна проверка на типовете, т.е. 200 не е ‘200’;
3) ако няма зададен default, и не влезе в никоя от възможностите, хвърля UnhandledMatchError exception;

Общо взето, основното му предимство е, че е по-стриктно откъм типове.

Constructor property promotion

Syntactic sugar. Цялата му философия се вижда от долният пример. Да не трябва да декларираш отделно клас пропъртитата, които сетваш в конструктора, а да става наведнъж – в конструктора.

In short: property promotion allows you to combine class fields, constructor definition and variable assignments all into one syntax, in the construct parameter list.

class Money
{
    public function __construct(
         public Currency $currency,
         public int $amount,
    ) {}
}

Важно е, че constructor property promotion НЕ е разрешено за абстрактни класове и интерфейси, но са разрешени за traits.

Също, НЕ са разрeшени т.н. „Variadic Parameters Are Not Allowed“ например:

class TestClass
{
    // Error: Variadic parameter
    public function __construct(public string ...$strings)
    {
        //...
    }
}

New static return type

New mixed type for both parameter and return type

Throw expression

Изключенията вече могат да се присвояват на променливи.

$triggerError = fn () => throw new MyError();

$foo = $bar['offset'] ?? throw new OffsetDoesNotExist('offset');

Inheritance with private methods

Досега PHP правеше проверка за signature както на public и protected методите, но така и на private, което e безсмислено и има RFC това да бъде премахнато.

Private методите няма смисъл да се декларират като final, защото те така или иначе не могат да се наследяват. Вече ще дава:
Warning: Private methods cannot be final as they are never overridden by other classes

Weak maps

SplObjectStorage е механизъм, с чиято помощ може да се създават колекции от обекти, като масиви, в които ключът е самият обект и евентуално може да има стойност, може и да няма. Системен клас, който си идва с набор от методи за управление на всичко това.

И като добавиш готов обект с метода attach(object $obj, mixed $value = null): void вече имаш рефърънс към този обект. И ако унищожиш този обект, например с unset($obj1), той ще е NULL но в SplObjectStorage ще съществува, което си е memory leak.

$obj1 = new stdClass();
$obj1->name = 'Foo'; $obj1->age = 28;

$map = new SplObjectStorage();

$map[$obj1] = 'Additional data for bla bla bla...';
var_dump($map->current());    // имаме си го

unset($obj1);       // Only clears reference

var_dump($obj1);    // NULL
echo count($map);   // 1
var_dump($map->current());   // имаме си го пак, нищо че оригиналът е ънсетнат

При WeakMap – не, изтрива се и от там, което е предимство откъм памет.
Демек:

$obj1 = new stdClass();
$obj1->name = 'Foo'; $obj1->age = 28;

$map = new WeakMap();

$map[$obj1] = 'Additional data for бла бла бла...';
var_dump($map->offsetGet($obj1));    // имаме си го

unset($obj1); // Removes $obj and the key and value from $map as well.

var_dump($obj1);    // NULL
echo count($map);   // 0
var_dump($map->offsetGet($obj1));   // нямаме си го, garbage collector го е забърсал

Иначе трябва изрично да изчистваш реферънса в SplObjectStorage с SplObjectStorage::detach(object $object): void

Allowing ::class on objects

За да вземеш класа на обект, вече не ти трябва get_class(). Може и с $foo::class

Non-capturing catches

Вече няма нужда в catch() да се задава променлива, ако не ти трябва.

try {
    // Something goes wrong
} catch (MySpecialException) {
    // Do something here
}

Trailing comma in parameter lists

Вече може да имаш запетая след последният параметър.

function patapan(string $parameterA, int $parameterB, Foo $objectfoo, )
{
    //...
}

Create DateTime objects from interface

New Stringable interface

Вграден интерфейс, който може да се използва като typehint за да зададе, че очакваната променлива например, трябва да може да се използва като string. Пък била тя string или обект с __toString() метод, който може да се използва като стринг.

function bar(string|Stringable $stringable)
{
    /* ... */
}

bar(new Foo());
bar('abc');

Вижда се, че на bar() можем да подадем както string скаларна променлива, така и обект, съдържащ __toString() магичн метод.

От примера се вижда, че е напълно нормално двата typehints да се използват заедно като uniontype.

New str_contains() function

Whether a string contains another string…

if (str_contains('string with lots of words', 'words')) { 
    /* ... */
}

New str_starts_with() and str_ends_with() functions

str_starts_with('haystack', 'hay'); // true
str_ends_with('haystack', 'stack'); // true

New fdiv() function

var_dump(fdiv(5.7, 1.3));    // float(4.384615384615385)
var_dump(fdiv(4, 2));        // float(2)
var_dump(fdiv(1.0, 0.0));    // float(INF)
var_dump(fdiv(-1.0, 0.0));   // float(-INF)
var_dump(fdiv(0.0, 0.0));    // float(NAN)

New get_debug_type() function

По-удобна версия на gettype(), която например дава името на класа и т.н…

TypeExample valuegettype()get_debug_type()
Class objectnew stdClass()objectstdClass
Class objectnew DateTime()objectDateTime
Class objectnew Foo\Bar()objectFoo\Bar
Closurefunction() {}objectClosure
Anonymous classnew class {}objectclass@anonymous
Anonymous subclassnew class extends Foo{}objectFoo@anonymous

New get_resource_id() function

Всяка ресурс променлива (разни хендлъри към бази данни…) имат ID, което до сега се вземаше като се тайпкастне ресурса към int.
Сега има тази специална функция.

Abstract methods in traits improvements

Трейтовете могат да декларират абстрактни методи, като е важно да се спомене, че спазванео на сигнъчъра е задължителен.

Object implementation of token_get_all()

Variable syntax tweaks

Type annotations for internal functions

Литература:

https://stitcher.io/blog/new-in-php-8#new-features

https://php.watch/articles/practical-weakmap

https://www.php.net/manual/en/class.splobjectstorage.php

https://www.php.net/manual/en/class.weakmap.php

PHP strlen() vs. mb_strlen()

strlen() брои броя байтове, които даденият стринг заема, което значи, че би върнала правилен резултат за енкодинги като ASCII, ANSI, UTF-8…

strlen() returns the number of bytes rather than the number of characters in a string.

mb_strlen() брои броя символи, но трябва да знае какъв на енкодинг е даденият стринг, за да знае как да ги преброи. If it is omitted or null, the internal character encoding value will be used. If the encoding is unknown, an error of level E_WARNING is generated.

Covariance and Contravariance in PHP

Ковариантност

Covariance allows a child’s method to return a more specific type than the return type of its parent’s method.

Whereas, contravariance allows a parameter type to be less specific in a child method, than that of its parent.

Ако си представим една йерархична структура от наследяващи се класове, covariance означава, че „децата“ връщат по-конкретен тип резултат.

По-просто, covariance „затяга конкретността“, contravariance – обратното.

Какво значи „more specific“ (по-голяма конкретност)?
1. когато не се използват т.н. union types (изброени с „|“ типове);
2. ако класът, който използваме за тип, има родител, не използваме родителя;
3. вместо float, използваме int (ако случаят го позволява разбира се), защото int е частен случай на float.

Както се вижда, това е основната логика зад понятието „ковариантност“.

Ако обърнем логиката обратно – към „разширяване“ или „намаляване на конкретността“ – това пък е „контравариантност“.

Ето много прост пример за ковариантност:

<?php

abstract class Animal
{
    public function __construct(protected string $name) { }
    abstract public function speak();
}

class Dog extends Animal
{
    public function speak(): void
{
        echo $this->name . " barks";
    }
}

class Cat extends Animal 
{
    public function speak(): void
{
        echo $this->name . " meows";
    }
}

// Lest have a simple factory, that produces cats or dogs...
function makeMeADog(): Dog
{
return new Dog('Bobi');
}

function makeMeACat(): Cat
{
return new Cat('Pisko');
}

Къде е ковариантността?
Можем да променим Animal да не е абстрактен, а нормален клас и да си „произвеждаме“ животни.
Но решаваме да сме по-конкретни, искаме не Животно, а конкретно Куче, Котка… и… това е.

Контравариантност

Пример за контравариантност би бил ако например в някой от оверрайдващите методи, зададем като тип на входен параметър – по-малко конкретен тип. Тоест, ако наследникът „разшири“ вместо да „затегне“ конкретността.

class Food { }

class AnimalFood extends Food { }

abstract class Animal
{
    public function __construct(protected string $name) { }

    public function eat(AnimalFood $food): void
{
        echo $this->name . " eats " . get_class($food);
    }
}

class Dog extends Animal
{
    public function eat(Food $food): void
 {
        echo $this->name . " eats " . get_class($food);
    }
}

Вижда се как в Dog, наследникът на Animal, в оверрайдващият метод eat() позволяваме входен параметър Food с по-малка конкретност.
Това ще да е някое всеядно куче 😉

Литература:

https://www.php.net/manual/en/language.oop5.variance.php

Class properties and methods with the same name

In PHP, methods and properties are in a separate namespace (you can have a method and a property with the same name).

Следователно можем да имаме:

class Value 
{
    private int $value;

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

    private function value() : int 
    {
        return $this->value * 2;
    }
}

Whether you are accessing a property or a method depends of the syntax you are using to do so.

$expr->value() is a method call, so PHP will search something in the class’ list of methods.

$expr->value is a property fetch, so PHP will search something in the class’ list of properties.

Writing PHP Unit test friendly code

Не използвай супер-глобални ($ _SESSION, $ _GET, $ _POST, etc…) или глобални променливи вътре в тестваната функция!

Със сигурност не използвай $ _SESSION, $ _GET, $ _POST вътре, защото Unit Test-овете се рънват през командният ред и така или иначе нямаш сесии, ГЕТ, ПОСТ…

public function getCurrentUser(){
    $user_id = $ _SESSION['user_id'];
    $user = App::db->select('id, username')
                ->where('id', $user_id)
                ->limit(1)
                ->get();

    if ($user->num_results() > 0) {
        return $user->row();
    }

    return false;
}

Например, не пиши:

Това не е testable.

Подавай го отвън това $user_id, за да можеш да тестваш тази функция, като и подаваш различни user_id-та.
А не така, хардкоднато.

2) Подобно на точка 1), не създавай обект вътре във функцията, която ще тестваш, създай го извън нея и и го подай като праметър (dependency injection).

3) Старай се една функция да има колкото може по малко параметри, защото всяка комбинация от параметри значи отделен тест сценарий.

4) Една функция трябва да прави само едно нещо, в смисъл, не трябва в една ф-я да има сейв в ДБ, пращане на емайл, дисплаей на някакаъв месидж…Демек, „всяка функция да си знае функцията“, както и жабата – гьола.

Източник: https://codeburst.io/writing-php-unit-testing-friendly-code-5ef9a8a49da1

Don’t assume global context

Don’t create new global variables

Rely only on direct inputs

Do not use exit() – недей да си гърмиш кода с die() или exit(), по-добре хвърляй Ексепшъни, и естествено, ги прихващай.

Източник: https://www.mediawiki.org/wiki/Manual:Writing_testable_PHP_code

Сравняване на float стойности в PHP

Никога не разчитайте на обикновеното сравняване когато сравнявате дроби. Това долу може и да не работи правилно и може и да изведе ‘Not equal’ и по принцип зависи от платформата, демек от хардуера. Как точно, не знам.

$a = 0.3;
$b = 0.4;
$c = 0.7;

if (($a + $b) === $c) {
    echo 'Equal';
} else {
    echo 'Not equal';
}

In PHP, the size of a floating values is platform-dependent. Due to internal representation of floating point numbers, there might be unexpected output while performing or testing floating point values for equality.

Дължи се на една особеност на компютрите – има винаги някаква малка загуба на точност когато съхраняват дроби (floating point numbers) в паметта си. И това идва при представянето на даденото число в двоична бройна система.

Например, десетичното 0.5 всъщност се съхранява двоично в паметта като 0.49999999… според стандарта IEEE 754 double precision. Това е неизбежно, защото когато превърнем десетичното 0.5 в двоично число, дори и IEEE 754 double precision не стига за събиране на всички „0“ и „1“ и от там идва загубата на точност, защото неизбежно се налага да „отрежем“ числото от някъде, за да го съберем.

Десетично 0.5 може да е ОК, но двоичното 0.5 може и да не е съвсем 0.5

Литература:

https://www.geeksforgeeks.org/comparing-float-value-in-php/

https://docstore.mik.ua/orelly/webprog/pcook/ch02_03.htm

https://www.php.net/manual/en/language.types.float.php

Methods overriding

Явно само public методи и пропъртита могат да се оверрайдват.

Това долу ще гръмне, независимо дали „детското“ alpha() е с видимост private или protected.

class Roditel
{
    public function alpha(): string
    {
        return '11111111';
    }
}
class Dete extends Roditel
{
    private function alpha(): string
    {
        return '2222222';
    }
}
$d = new Dete();
echo $d->alpha();

Fatal error: Access level to Dete::alpha() must be public (as in class Roditel) in […][…] on line 13

PHP short circuit

PHP has indeed a mechanic named short-circuit:
http://php.net/manual/en/language.operators.logical.php#example-140

PHP interpreter is „lazy“, meaning it will do the minimum number of comparisons possible to evaluate conditions.

Ако в много OR условия, първият, който даде TRUE, останалите не се проверяват.

Аналогично за много AND условия, когато първият даде FALSE, останалите също не се проверяват.

Интерфейсите не могат да имат пропъртита

PHP interfaces can have constants, but not properties (instance variables).

Interfaces in PHP may only contain public method signatures without a method body. They may also contain constants. But that’s it. Nothing else.

PHP type declarations case sensitive?

Не, не са, но според psr-12 e добре да са lowercase.

All PHP reserved keywords and types [1][2] MUST be in lower case.

Any new types and keywords added to future PHP versions MUST be in lower case.

Short form of type keywords MUST be used i.e. bool instead of booleanint instead of integer etc.

urlencode vs. rawurlencode

The difference being the asd%20asd vs asd+asd

urlencode differs from RFC 1738 by encoding spaces as + instead of %20

–––––––––––––––––––––––––––––-

One practical reason to choose one over the other is if you’re going to use the result in another environment, for example JavaScript.

In PHP urlencode('test 1') returns 'test+1' 
while rawurlencode('test 1') returns 'test%201' as result.

But if you need to „decode“ this in JavaScript using decodeURI() function then decodeURI("test+1") will give you "test+1" while decodeURI("test%201") will give you "test 1" as result.

In other words the space (“ „) encoded by urlencode to plus („+“) in PHP will not be properly decoded by decodeURI in JavaScript.

In such cases the rawurlencode PHP function should be used.

––––––––––––––––––––––––––––––––-

The only difference is in the way spaces are treated:

urlencode – based on legacy implementation converts spaces to +

rawurlencode – based on RFC 1738 translates spaces to %20

So my advice is to use rawurlencode to produce standards compliant RFC 1738 encoded strings and use urldecode to be backward compatible and accomodate anything you may come across to consume.

––––––––––––––––––––––––––––––––––

Явно наистина само енкодването на спейса е разликата, защото като пробваям нещо по-сложно като например:

<?php
echo urlencode('test 1+2-3.4/5'); 
echo PHP_EOL;
echo rawurlencode('test 1+2-3.4/5');

пак разликата е в „+“ и „%20“

https://stackoverflow.com/questions/996139/urlencode-vs-rawurlencode

Equal routes patterns

Внимавай когато дефинираш раутове с еднакви патърни, защото по принцип първо се гледа path-а да мачне и тогава вече се гледа HTTP метода.

Ако имаш например POST и GET route с еднакви path-ове, винаги ще ти мачва първият поред.

Има логика, защото например като напишеш www.asdasd.com първо трябва да намерим сървъра и изобщо търсеният ресурс, тогава вече го достъпваме по даденият метод.

php yield

Разликата между :

function patapan(): int
{
    for ($i = 1; $i <= 3; $i++) {
        return $i;
    }
}

и

function patapan()
{
    for ($i = 1; $i <= 3; $i++) {
        yield $i;
    }
}

е, че първият случай „patapan()“ ако го използваш така:
echo patapan();
echo patapan();
echo patapan();
echo patapan();

ще върне
1 1 1 1 …
защото всеки път ще започва for-а отначало.

При вторият, patapan() ще върне обект от интерфейс Generator, който трябва да се изцикли и при всяка итерация ще return-ва поредната стойност.
Демек, ще пази до къде е стигнал for-а за да може да не започва всеки път от начало. Но как програмно става това „пазене“ на това, до къде сме стигнали?
Отоворът се крие в Iterator Design Pattern.

function patapan(): Generator
{
for ($i = 1; $i <= 3; $i++) {
yield $i;
}
}

$ptpns = patapan();
foreach ($ptpns as $ptpn) {
echo $ptpn;
}
// 1 2 3

Функция, която имплементира Generator интерфейса, е напълно обикновена функция, с тази разлика, че вместо да върне резултат подобно на всяка функция, тя връща обект, който може да се изцикли, например с най-обикновен foreach, и при всяка итерация ще получаваме текущата стойност, като също ще се запазва до къде сме стигнали.

Удобно е, в смисъл, че най-малкото страшно пести памет, когато трябва да се използват например резултати от DB SELECT заявки, защото дава възможност да се използва резултатът един вид „на части“, „per iteration basis„,
демек, не ми давай наведнъж всичко, а ми давай резултата едно по едно като разбира всеки път си пази до къде си стигнал, за да знаеш кое ще е следващото.

Или с други думи, „по-ефектифен начин да връщаш стойност докато още изцикляш всички стойности“ – (that will calculate and return values while you are looping over it)

Когато използваме обикновена фукнкция, натрупваме резултата и тогава го връщаме с return.
Koгато използваме yield – започваме да изпълняваме и стигнем ли то yield – връщаме каквото е в момента на дадената итерация, запазваме до къде сме стигнали и следващата итерация не се повтаряме.

PHP тайпкаствания

$x1 = 16 + 4 + 'V4olvo';

$x1 ще е int(20), защото в PHP, за разлика от JS, плюсът е само аритметичен оператор. Започва от ляво на дясно, събира първите две, после вижда, че трябва да добави и стринг. Ако този стринг поне ЗАПОЧВА с число – ще вземе само това число, иначе ще смята тоз стринг за 0, дори и вътре в стринга да има числа.

Затова например това:

$x2 = 16 + 4 + '4Volvo';  // x2 ще е int(24), демек все едно 16
$x3 = 'Volvo' + 16 + 4;   // x3 ще е int(20), демек все едно 0 + 16 + 4

//var_dump(16 . 4);die; // string(3) „164“ явно операторът (конкатениране) предварително ги каства към стрингове

//var_dump(’16’ + ‘4’);die; // int(20) явно операторът (събиране) предварително ги каства към числа

Цялата работа е следната: то (PHP-то), започва от ляво на дясно, гледа оператора, и се опитва да кастне към число или стринг.

Демек, вижда ЕДНОНЕЩО + ДРУГОНЕЩО и разсъждава така: „Имам плюс, демек аритметичен оператор, значи трябва да събирам числа. Но това не са числа. Ми дай да опитам да ги направя числа.“ И до колкото успее – успее.

Аналогично – и при точката (конкатенацията), подавай му каквито щеш операнди, то като види, че трябва да конкатенира – първо ще опита да ги кастне към стрингове.

Затова се казва, че в PHP операторът „коли и беси“

$x4 = 16 + 4 . ‘Volvo’; // x4 ще е string „20Volvo“ – явно както в JS, ги започва от ляво надясно, и понеже първо има събиране – каства операндите към числов тип (то в сличая няма нужда де, но все пак), после вижда „я! конкатениране, айде всичко до тука – стринг, и тогава конкатенира“. И така и останалите, ако има такива.

$x5 = ‘Volvo’ . 16 + 4; // x5 ще е int 4 – явно първо конкатенира Volvo с 16, което прави ‘Volvo16’, и после се опитва да събира с 4, но понеже Volvo16 не започва с цифра, става на 0 и тогава събира с 4

$cc = 100 + '100';     var_dump($cc);   // int(200)
$mc = '100' + 100;     var_dump($mc);   // int(200)
$fc = 100 + 100;       var_dump($fc);   // int(200)

new self() vs. new static() and early/late static binding

Първо, нека да изсним терминът „binding“, що е то и има ли почва у нас.

Binding значи когато извикаш даден метод на обект, да е зададено кой код да се изпълни. Демек, зад дадаено име, параметри и връщана стойност (сигнъчъра), да се знае кой код да стои. Kaто под „кой код“ се има предвид адрес от паметта, от където започва въпросният код, като да не забравяме, че трябва да имаме и адрес в паметта, където да се върнем, след като сме изпълнили кода на даденият метод.

Binding е един вид „мостът“ между сигнъчърът (комплексно понятие от името, параметрите с техните типове, и разбира се – типът на връщаният резултат). Това е един вид уникалният идентификатор за даден метод, не в буквалният смисъл, а по-скоро по име, по параметри и техните типове, както и типът на връщаният резултат, изобщо сигнъчърът.

Как може да стане това?
Или динамично, или статично. В какъв смисъл? В смисъл, че кой точно код стои зад даденият сигнъчър може да се зададе compiletime (твърдо), или runtime, демек по време на изпълнеие.

На каква база?

На база сигнъчъра, демек че може методи с еднакви имена да имат различни параметри, или тип на връщан резултат. И при извикване на метод по име, да се реши кой точно код да се изпълни, на базата освен на името му, но и на гореспоменатите неща (сигнъчъра).

И тук идва самата идея на т.н. static и dynamic binding.

Static `binding /или още early или compiletime/ е когато още по време на компилация е ясно при извикване на кой „сигнъчър“, кой код да се изпълни. Твърдо. Това е т.н. overloading, тоест когато предварително зад кой сигнъчър, компилаторът знае кой код съответства. Дори и имената на методите да са еднакви.
Но както знаем overloading не се поддържа от PHP, и до колкото знам няма и тенденция да се поддържа.

Да разгледаме следният пример:

class GrandMother
{
    public static function getSelf(): self
    {
        return new self();
    }

    public static function getStatic(): self
    {
        return new static();
    }
}

class Mother extends GrandMother
{
}

class Child extends Mother
{
}

var_dump(GrandMother::getSelf());     // object(GrandMother)#1 (0) {}
var_dump(GrandMother::getStatic());   // object(GrandMother)#1 (0) {}

echo "\n\n";

var_dump(Mother::getSelf());          // object(GrandMother)#1 (0) {}
var_dump(Mother::getStatic());        // object(Mother)#1 (0) {}

echo "\n\n";

var_dump(Child::getSelf());           // object(GrandMother)#1 (0) {}
var_dump(Child::getStatic());         // object(Child)#1 (0) {}

Тоест, static() сочи къде методът е бил ИЗВИКАН, a self() – къде е ДЕФИНИРАН.

Ето още един пример:

class Car
{
    protected static $helloString = 'I am a car';

    public static function print(): void
    {
        echo self::$helloString;
    }
}

class FlyingCar extends Car
{
    protected static $helloString = 'I am a flying car';
}

FlyingCar::print();

Ще изведе ‘I am a car’, защото като нямаме print() за FlyingCar, PHP ще извика този на родителя – Car, и понеже имаме self, това значи „използвай този клас, защото аз, print(), съм дефиниран тук, а не в наследника“, ще използваме съответно и пропъртито $helloString на Car.
Независимо, че сме извикали print() през наследника. За self е важно къде сме го дефинирали, не от къде сме го извикали.

Но вече, ако беше echo static::$helloString; тогава вече ще вземем $helloString на класа, през който викаме print(). Затова ще изведе ‘I am a flying car’.

Затова първият пример се казва „Early static binding„, а вторият – „Late static binding„.

При early имаме задаване на стойностите на пропъртитата още „at compile time“, при late – „at execution time“.

Къде е тънкостта?
В принципната разлика между self и static.

Защо?
Защото self още при компилиране твърдо задава, че е binded (обвързано, сочи към) с класа, В КОЙТО Е ДЕФИНИРАН.
А static не се „обвързва“ твърдо със своя клас, при него динамично се решава с кой клас да се обвърже.

Защо хем static, хем late?
„Late binding“ comes from the fact that static:: will not be resolved using the class where the method is defined but it will rather be computed using runtime information. It was also called a „static binding“ as it can be used for (but is not limited to) static method calls.

Литература

https://www.educative.io/answers/what-are-early-binding-and-late-binding-functions-in-cpp

Kлас-константите и интерфейс константите са…

Kлас-константите са СТАТИЧНИ по принцип.

Може да ги аксесваш през клас, напр: (Klass:CONST_NAME).

Mоже през обект, но статично ($p::CLASS_NAME),

Но не и динамично (демек per object basis) – $p->CLASS_NAME, защото клас константите не могат да са спечифични за всеки обект.

Същото важи и за интерфейсните константи.

PHP lazy copy, or „copy-on-write“

https://stackoverflow.com/questions/746224/are-there-pointers-in-php

In PHP, arguments are passed by value by default. So when calling a function, when you pass in your values, they are copied by value not by reference.

There is something interesting to note though. Because pass-by-value mode could result in more memory usage, and PHP is an interpreted language (so programs written in PHP are not as fast as compiled programs), to make the code run faster and minimize memory usage, there are some tweaks in the PHP interpreter.

Which means, when you are coping a variable into another variable, PHP will copy a reference to the first variable into the second variable. So your new variable, is actually a reference to the first one until now. The value is not copied yet. But if you try to change any of these variables, PHP will make a copy of the value, and then changes the variable. This way you will have the opportunity to save memory and time, IF YOU DO NOT CHANGE THE VALUE.

return $object е по принцип безсмислено ако го предаваш като параметър, защото всъщност предаваш рефърънс, а не самият обект, и каквото и да правиш с този обект, остава за него глобално. Но тук е важно да се припомни, че важи едно нещо, наречено lazy copy. В какъв смисъл?
Нека разгледаме следните два примера:

$o = new stdClass;
$o->ala = 123;
var_dump($o);   // object(stdClass)#1 (1) {
                //      ["ala"]=> int(123)
                // }

function patapan1(stdClass $obj): void
{
    $obj->bala = 456;
}
patapan1($o);
var_dump($o);
    // object(stdClass)#1 (2) {
    //      ["ala"]=>    int(123)
    //      ["bala"]=>   int(456)
    // }

//---------------------------------------------------

$a = [111, 222];
var_dump($a);  // array(2) {
               //     [0]=>    int(111)
               //     [1]=>    int(222)
               // }

function patapan2(array $arr): void
{
    $arr[] = 456;
}
patapan2($a);
var_dump($a);  // array(2) {
               //     [0]=>   int(111)
               //     [1]=>   int(222)
               // }

Когато предаваме обект като аргумент на функция, винаги се предава реферънсът към този обект. Каквито и промени да правим с този обект вътре във функцията (включително unset-ване на пропърти), те се оказват всъщност глобални, извън скоупа на функцията, защото са промени по рефърънс, не по стойност.

При масивите е доста по-различно, и всъщност lazy copy важи за тях, не за обектите. В смисъл, че като предадеш масив като аргумент на функция, пак предаваш реферънс, подобно на обектите, но внесеш ли каквато и да е промяна в този предаден масив, (unset-ване, добавяне на елемент…), тогава вече PHP създава копие на масива със скоуп, локален за функцията. Тогава вече тези промени не важат извън функцията и ако искаш да ги имаш, трябва да return-неш масива.

Масивите по дефолт по реферънс ли се предават?

https://stackoverflow.com/questions/2030906/are-arrays-in-php-passed-by-value-or-by-reference

Тhe array is passed by reference UNLESS it is modified within the method / function you’re calling. If you attempt to modify the array within the method / function, a copy of it is made first, and then only the copy is modified. This makes it seem as if the array is passed by value when in actual fact it isn’t.

Еven though you aren’t defining your function to accept $my_array by reference (by using the & character in the parameter definition), it still gets passed by reference (ie: you don’t waste memory with an unnecessary copy).

However if you modify the array, a copy of it is made first (which uses more memory but leaves your original array unaffected).

FYI – this is known as „lazy copy“ or „copy-on-write“.