diff --git a/README.md b/README.md
index 877075e..86cf064 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,20 @@
# Compass
-> Библиотека работает с URL-адресами и с путями к файлам на диске. Compass написана на PHP и предлагает набор методов
-> для обработки этих двух типов данных. Она может быть использована в различных приложениях, таких как веб-приложения
-> для формирования и обработки путей к файлам или системы управления контентом для создания URL-адресов.
+> Compass - это библиотека, предназначенная для работы с URL-адресами и путями к файлам на жестком диске. В ее состав
+> входят инструменты для упрощения манипуляций с этими данными. Цель библиотеки - облегчить работу с URL-адресами и
+> данными о расположении файлов.
## Общая информация
-Эта библиотека предназначена для использования в более высокоуровневых PHP-проектах и включает в себя два основных
-компонента: для работы с путями к файлам (`\Compass\Path`) и для работы с URL (`\Compass\Url`). Оба эти
-компонента представлены отдельными объектами и предлагают набор методов, упрощающих манипуляции с путями и URL-адресами.
+Compass может быть использован в различных PHP-приложениях для обработки путей к файлам и URL-адресов. Он включает в
+себя два основных компонента: `Compass\Path` и `Compass\Url`. Оба этих компонента представляют собой отдельные объекты
+и предлагают набор методов для простого манипулирования данными. `Compass\Path` предоставляет инструменты для работы с
+директориями, включая поиск, замену и проверку наличия директорий в строке пути, а также изменение их порядка. В свою
+очередь, `Compass\Url` обеспечивает возможности для работы с URL, позволяя создавать, получать и изменять различные
+части URL-адреса.
-`\Compass\Path` помогает упростить работу с директориями и файлами, позволяя находить и заменять определенные
-директории по указанному шаблону, а также проверять наличие директорий в пути и изменять их порядок. Этот компонент
-предоставляет возможность быстро и эффективно выполнять различные манипуляции с путями.
-
-В свою очередь, `\Compass\Url` позволяет работать с URL, предоставляя набор методов для создания, получения и
-изменения различных частей URL-адреса. Особенностью этого компонента является то, что он создает URL оптимальным
-образом, изменяя только те части адреса, которые требуется изменить, при создании множества URL одного домена.
-
-Схема показывает какие данные можно получить и модифицировать:
-
-```
- |---------------------------------------absolute url---------------------------------|
- | |
- |-----------------base url----------------|------------------relative url------------|
- | | |
- | authority | path query fragment|
- | /‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\|/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\ /‾‾‾‾‾‾‾‾\ /‾‾‾‾‾‾\|
- |http://grigor:password@vinograd.soft:8080/path/to/resource.json?query=value#fragment|
- \__/ \___/ \_____/ \___________/ \__/ \___/
- scheme user password host port suffix
-```
-
-Оба компонента работают по схожему принципу, сначала собирая необходимые параметры, а затем применяя их к
-результату с помощью специального метода `updateSource()`.
+Оба компонента работают по схожему принципу, сначала собирают необходимые параметры, а затем применяют их к результату с
+помощью специального метода `updateSource()`.
## Установка
@@ -53,109 +34,354 @@ php composer require vinogradsoft/compass "^1.0"
```php
setScheme('https')
- ->setUser('grigor')
- ->setPassword('pass@word')
- ->setHost('host.ru')
- ->setPort('8088')
- ->setPath('/user/index')
- ->setSuffix('.php')
- ->setArrayQuery([
- 'key1' => 'value1',
- 'key2' => 'value2'
- ])->setFragment('fragment');
+$url->setScheme('http')->setUser('grigor')->setPassword('password')->setHost('vinograd.soft')
+ ->setPort('8080')->setPath('/path/to/resource')->setSuffix('.json')
+ ->setArrayQuery(['query' => 'value',])->setFragment('fragment');
$url->updateSource();
-printUrl($url);
+echo '
Authority: ', $url->getAuthority();
+echo '
Base Url: ', $url->getBaseUrl();
+echo '
Relative Url: ', $url->getRelativeUrl();
+echo '
Absolute Url: ', $url->getSource(), '
';
+echo '
Scheme: ', $url->getScheme();
+echo '
User: ', $url->getUser();
+echo '
Password: ', $url->getPassword();
+echo '
Host: ', $url->getHost();
+echo '
Port: ', $url->getPort();
+echo '
Path: ', $url->getPath();
+echo '
Suffix: ', $url->getSuffix();
+echo '
Query: ', $url->getQuery();
+echo '
Fragment: ', $url->getFragment();
+
+$url->setSource('http://россия.рф');
+$url->setConversionIdnToAscii(true)->updateSource();
+echo '
new URL: ',$url; #http://xn--h1alffa9f.xn--p1ai
+```
-$url = new Url('https://grigor:pass%40word@host.ru:8088/user/index?key1=value1&key2=value2#fragment');
-$url->setSuffix('.php');
-$url->updateSource();
+### Создание экземпляра класса Compass\Url
+
+Объект класса `Compass\Url` может быть создан путем вызова статического метода `createBlank()`, либо с помощью
+оператора `new`. Метод `createBlank` примечателен тем, что в своей работе применяет клонирование своего же
+прототипа. У данного метода есть два необязательных параметра: `$isIdnToAscii`, `$updateStrategy` - они определяют,
+будет ли происходить преобразование хоста в `punycode`, и какую стратегию обновления URL применять.
+
+Первый параметр, `$isIdnToAscii` - определяет необходимость преобразования хоста. Если он принимает значение `true`,
+хост преобразуется, если же `false` - преобразование хоста не происходит.
+
+Второй параметр, `$updateStrategy` - определяет стратегию обновления URL. Стратегия включает в себя методы создания
+составляющих URL-адреса.
+
+Чтобы создать новый экземпляр класса `Compass\Url` с помощью оператора `new` необходимо передать один
+обязательный параметр - это исходный URL в виде строки.
+
+### Варианты сборки URL
+
+Параметры можно устанавливать с помощью:
+
+- конструктора
+- метода `$url->setSource(string $src);`
+- метода `$url->setAll(array $parts);`
+- методов отвечающих за конкретную часть Url (как показано в быстром старте).
+
+### Примеры
+
+#### **Через конструктор**
+
+```php
+$url = new Url('http://grigor:password@vinograd.soft:8080/path/to/resource?query=value#fragment');
+```
+
+#### **С помощью метода `$url->setSource(string $src);`**
+
+```php
+$url->setSource('http://grigor:password@vinograd.soft:8080/path/to/resource?query=value#fragment');
+```
+
+#### **С помощью метода `$url->setAll(array $parts);`**
+
+```php
+$url->setAll([
+ ':host' => 'host.ru',
+ ':scheme' => 'http',
+ ':user' => 'user',
+ ':password' => 'password',
+ ':port' => '80',
+ ':path' => ['path', 'to', 'resource'],
+ '?' => ['key' => 'value', 'key2' => 'value2'],
+ '#' => 'fragment',
+ ':suffix' => '.json',
+]);
+```
+
+#### **С помощью методов отвечающих за конкретную часть Url.**
+
+```php
+$url->setScheme('http')->setUser('grigor')->setPassword('password')->setHost('vinograd.soft')
+ ->setPort('8080')->setPath('/path/to/resource')->setSuffix('.json')
+ ->setArrayQuery(['query' => 'value',])->setFragment('fragment');
+```
+
+В первых двух вариантах суффикс не распознается. В таких случаях суффикс необходимо установить отдельным методом
+`$url->setSuffix(.json);`, только так вы сможете им управлять.
+
+> Суффикс не анализируется, так как он может быть любой строкой, и не обязательно начинаться с точки. Если вы передаете
+> URL с суффиксом, то он становится частью path.
-printUrl($url);
+### Применение изменений
+
+Чтобы измененные параметры вступили в силу, необходимо вызвать метод `$url->updateSource()`. Этот метод имеет два
+необязательных аргумента - `$updateAbsoluteUrl` и `$suffix`. Значение аргумента `$updateAbsoluteUrl` определяет, будет
+ли обновляться весь URL-адрес или только его относительная часть. По умолчанию его значение равно `true`, другими
+словами, по умолчанию будет пытаться обновить весь URL. Параметр `$suffix` имеет тип `string` и позволяет установить
+суффикс в моменте обновления.
+
+После того как параметры были применены, вы можете получить обновленный результат, методом `$url->getSource()`.
+
+### Стратегии обновления
+
+> ***Стратегия обновления*** - это объект, который объединяет все входные данные для создания итогового URL-адреса.
+> Этот объект должен быть реализацией интерфейса `Compass\UrlStrategy`. В системе, класс, который выполняет эту функцию,
+> называется `Compass\DefaultUrlStrategy`.
+
+Стратегия придумана, для того, чтобы контролировать процесс создания URL. Она содержит набор методов по одному
+на каждый участок URL-адреса, в которых происходит склеивание параметров. Какие методы будут участвовать в
+обновлении определяется в объекте `Compass\Url` на основе состояний участков. Есть некие флаги которые говорят какие
+участки нужно пересоздать. Для простоты восприятия можно провести аналогию с дорогой поделенной на какое-то количество
+участков "A", "B", "C", "D" и дорожной компанией которая за нее ответственна. В идеале дорога должна быть всегда ровная.
+Когда на дороге повреждается какой-нибудь участок, например "С", ремонтная бригада выезжает на этот участок и
+ремонтирует его. Так же можно представить и URL-адрес, где дорога это строка у которой есть логические части - участки.
+Ремонтные бригады - это методы в стратегии обновления. Дорожная компания - это объект `Compass\Url`.
+
+У стратегии есть шесть методов в которых создаются части URL-адреса:
+
+- `updateAuthority()`
+- `updateBaseUrl()`
+- `updateQuery()`
+- `updatePath()`
+- `updateRelativeUrl()`
+- `updateAbsoluteUrl()`.
+
+Устанавливая какой-либо параметр для URL, система меняет состояние участка, как бы, повреждает тот
+участок для которого был передан параметр. После вызова метода `$url->updateSource();` в работу включаются
+соответствующие методы стратегии.
+
+> Важно запомнить, что в объекте `Compass\Url` хранятся исходные части, которые передал пользователь и результаты работы
+> каждого метода стратегии.
+
+Выполнение методов можно поделить условно на три уровня.
+
+```
+ УРОВЕНЬ 3
+ |--------------------------------updateAbsoluteUrl()---------------------------------|
+ | |
+ | УРОВЕНЬ 2 |
+ |---------------updateBaseUrl()-----------|------------updateRelativeUrl()-----------|
+ | | |
+ | УРОВЕНЬ 1 |
+ | updateAuthority() | updatePath() updateQuery() |
+ | /‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\|/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\ /‾‾‾‾‾‾‾‾\ |
+ |http://grigor:password@vinograd.soft:8080/path/to/resource.json?query=value#fragment|
+```
-function printUrl($url)
+Метод третьего уровня `updateAbsoluteUrl()` выполняется всегда, в нем соединяются сохраненные результаты работы методов
+второго уровня. Методы второго уровня `updateBaseUrl()` и `updateRelativeUrl()` соединяют результаты работы первого
+уровня. На первом уровне `updateAuthority()`, `updatePath()` и `updateQuery()` склеивают исходные части каждый в своей
+области.
+
+### Манипуляция состояниями
+
+Состояния хранятся в нескольких полях класса `Compass\Url`. `$authoritySate`, `$relativeUrlState` и `$schemeState`.
+`$authoritySate` и `$relativeUrlState` имеют тип `int`, `$schemeState` - `bool`. Управление состояниями `$authoritySate`
+и `$relativeUrlState` осуществляются битовыми операциями. Вот пример кода, который показывает их значения по умолчанию
+когда состояние неповрежденное:
+
+```php
+const USER_STATE = 1 << 0;
+const PASSWORD_STATE = 1 << 1;
+const HOST_STATE = 1 << 2;
+const PORT_STATE = 1 << 3;
+
+const PATH_STATE = 1 << 0;
+const QUERY_STATE = 1 << 1;
+const FRAGMENT_STATE = 1 << 2;
+
+const AUTHORITY_WHOLE = self::USER_STATE | self::PASSWORD_STATE | self::HOST_STATE | self::PORT_STATE;
+const RELATIVE_URL_WHOLE = self::PATH_STATE | self::QUERY_STATE | self::FRAGMENT_STATE;
+
+/**
+ * current states
+ */
+protected int $authoritySate = self::AUTHORITY_WHOLE;
+protected int $relativeUrlState = self::RELATIVE_URL_WHOLE;
+protected bool $schemeState = true;
+```
+
+Если мы хотим повредить состояние `$relativeUrlState` в области `query`, то мы можем воспользоваться побитовыми
+операторами:
+
+```php
+$relativeUrlState &= ~Url::QUERY_STATE;
+```
+
+Остальными участками можно манипулировать аналогичным способом, исключением является только `$schemeState` ему нужно
+присвоить булево значение.
+
+### Пример создания своей стратегии
+
+При построении своего процесса обновления URL, иногда требуется, чтобы выполнился метод который на основе текущего
+состояния не выполниться. В таких случаях используется дополнительный метод `forceUnlockMethod(...)` в котором можно
+изменить текущее состояние определенного участка, тем самым заставить систему выполнить нужный метод.
+
+Это лучше понять на примере. Представим, что нам нужно генерировать URL для реферальных ссылок. Создадим стратегию в
+которой будет добавляться для всех URL с доменом `another.site` параметр `refid` равный `40`.
+
+Код стратегии:
+
+```php
+;
+
+use Compass\DefaultUrlStrategy;
+use Compass\Url;
+
+class ReferralUrlStrategy extends DefaultUrlStrategy
{
- echo '
Authority: ', $url->getAuthority();
- echo '
BaseUrl: ', $url->getBaseUrl();
- echo '
RelativeUrl: ', $url->getRelativeUrl();
- echo '
AbsoluteUrl: ', $url; //$url->getSource();
- echo '
';
-
- echo '
getScheme: ', $url->getScheme();
- echo '
getUser: ', $url->getUser();
- echo '
getPassword: ', $url->getPassword();
- echo '
getHost: ', $url->getHost();
- echo '
getPort: ', $url->getPort();
- echo '
getPath: ', $url->getPath();
- echo '
getSuffix: ', $url->getSuffix();
- echo '
getQuery: ', $url->getQuery();
- echo '
getFragment: ', $url->getFragment();
+
+ private bool $isAllowInsertParam = false;
+
+ /**
+ * @inheritDoc
+ */
+ public function updateQuery(array $items): string
+ {
+ if ($this->isAllowInsertParam) {
+ $items['refid'] = 40;
+ $this->isAllowInsertParam = false;
+ }
+ return http_build_query($items, '', '&', PHP_QUERY_RFC3986);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function forceUnlockMethod(
+ bool &$schemeState,
+ int &$authoritySate,
+ int &$relativeUrlState,
+ array $items,
+ array $pathItems,
+ array $queryItems,
+ bool $updateAbsoluteUrl,
+ ?string $suffix = null
+ ): void
+ {
+ if ($items[Url::HOST] === 'another.site') {
+ $this->isAllowInsertParam = true;
+ }
+ $relativeUrlState &= ~Url::QUERY_STATE;
+ }
+
}
```
-Класс `\Compass\Url` может быть создан путем вызова статического метода `createBlank()` либо созданием нового
-объекта оператором `new`. Метод `createBlank` примечателен тем, что в своей работе применяет клонирование своего же
-прототипа. У данного метода есть два необязательных параметра: `$isIdnToAscii`, `$updateStrategy` - они определяют,
-будет ли происходить преобразование хоста в `punycode`, и какую стратегию обновления/создания URL применять.
+Теперь установим стратегию и выведем два URL, один из которых будет целевым, с доменом `another.site`:
-Первый параметр, `$isIdnToAscii` - определяет необходимость преобразования хоста при создании URL. Если он принимает
-значение `true`, хост преобразуется, если же `false` - преобразование хоста не происходит. Этот параметр можно изменить
-после создания экземпляра класса через метод `setConversionIdnToAscii()` - ему нужно передать новое значение.
+```php
+$url = Url::createBlank();
+$url->setUpdateStrategy(new ReferralUrlStrategy());
-Второй параметр, `$updateStrategy` - определяет стратегию обновления URL или его создания. Он включает в себя выбор
-метода создания URL и его составляющих.
+$url->setSource('https://another.site');
+$url->updateSource();
+echo $url->getSource(), '
'; # https://another.site/?refid=40
-Чтобы создать новый экземпляр класса `\Compass\Url` с помощью оператора `new` необходимо передать один
-обязательный параметр - это исходный URL в виде строки.
+$url->setHost('vinograd.soft');
+$url->updateSource();
+echo $url->getSource(); # https://vinograd.soft
+```
-После настройки всех параметров для внесения изменений необходимо вызвать метод `updateSource();`.
+На первый взгляд может показаться, что изменение состояния (`$relativeUrlState &= ~Url::QUERY_STATE;`) нужно было
+написать внутри `if` конструкции, но это не так. После того как мы установили хост `vinograd.soft`, вызов
+метода `updateSource()` не привел бы к выполнению метода `updateQuery` нашей стратегии, поскольку не было добавлено ни
+одного параметра штатным способом, который мог бы изменить состояние этого участка. В результате состояние осталось бы
+не поврежденным, обновился бы только участок `baseurl` и сохраненный параметр `refid=40` с прошлого раза, был бы склеен
+с новым `baseurl` который содержит хост `vinograd.soft`.
+Этот пример показывает, что вы не ограничены только штатными методами установки частей URL. Используя стратегии можно
+делать пост обработку результата, например когда нужно экранировать результат для HTML-атрибутов содержащих URL (href,
+src и другие атрибуты этого типа).
---
## Компонент PATH
-### Быстрый старт
+`Compass\Path` можно охарактеризовать как объектное представление пути к файлу. Он оперирует строкой пути не
+опираясь на реальную файловую систему. Компонент так же как и `Compass\Url` имеет стратегию обновления, которая включает
+в себя один метод `updatePath()`. Важно отметить, что этот компонент не имеет состояний.
-```php
+### Демонстрация методов
+
+```php
replaceAll([
'__NAME__' => 'User',
'__NAME2__' => 'Filesystem',
]);
$path->updateSource();
-echo '
', $path;
-$path->setAll(['path','to','file.txt']);
+echo '
', $path; # /User/UserScanner/FilesystemDriver
+
+$path->setAll(['path', 'to', 'file.txt']);
$path->updateSource();
-echo '
', $path;
-```
-Результат вывода:
+echo '
', $path; # path/to/file.txt
-```
-/User/UserScanner/FilesystemDriver
-path/to/file.txt
-```
+$path->set(1, 'newTo');
+$path->updateSource();
----
+echo '
', $path; # path/newTo/file.txt
+
+$path->setBy('path', 'newPath');
+$path->updateSource();
+
+echo '
', $path; # newPath/newTo/file.txt
+
+echo '
', $path->dirname(); # newPath/newTo
+
+$path->setSuffix('.v');
+$path->updateSource();
+echo '
', $path; # newPath/newTo/file.txt.v
+
+$path->setSource('newPath/newTo/file');
+$path->setSuffix('.v');
+$path->updateSource();
+echo '
', $path; # newPath/newTo/file.v
+
+$path->replace('newPath','path');
+$path->updateSource();
+echo '
', $path; # path/newTo/file.v
+```
## Тестировать
``` php composer tests ```
+
+
+### Содействие
+Пожалуйста, смотрите [ВКЛАД](https://github.com/vinogradsoft/compass/blob/master/CONTRIBUTING.md) для получения подробной информации.
+
+### Лицензия
+Лицензия MIT (MIT). Пожалуйста, смотрите [файл лицензии](https://github.com/vinogradsoft/compass/blob/master/LICENSE) для получения дополнительной информации.
\ No newline at end of file