Skip to content

Commit

Permalink
add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Baptouuuu committed Mar 14, 2021
1 parent f6abb6a commit c86fd6f
Show file tree
Hide file tree
Showing 12 changed files with 636 additions and 0 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/documentation.yml
@@ -0,0 +1,26 @@
name: Documentation

on:
push:
branches: [master]

jobs:
publish:
runs-on: ubuntu-latest
name: 'Publish documentation'
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
- name: Install halsey/journal
run: composer global require halsey/journal
- name: Generate
run: composer global exec 'journal generate'
- name: Push
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./.tmp_journal/
61 changes: 61 additions & 0 deletions .journal
@@ -0,0 +1,61 @@
<?php
declare(strict_types = 1);

use Halsey\Journal\{
Config,
Menu\Entry,
};
use Innmind\Url\Path;

return static function(Config $config): Config {
return $config
->package('innmind', 'operating-system', null, 'OperatingSystem')
->menu(
Entry::markdown(
'Getting started',
Path::of('readme.md'),
),
Entry::section(
'Use cases',
Entry::markdown(
'Manipulating time',
Path::of('use_cases/time.md'),
),
Entry::markdown(
'Filesystem',
Path::of('use_cases/filesystem.md'),
),
Entry::markdown(
'HTTP Client',
Path::of('use_cases/http.md'),
),
Entry::markdown(
'Processes',
Path::of('use_cases/processes.md'),
),
Entry::markdown(
'Inter Process Communication',
Path::of('use_cases/ipc.md'),
),
Entry::markdown(
'Socket communication',
Path::of('use_cases/socket.md'),
),
Entry::markdown(
'Handling process signals',
Path::of('use_cases/signals.md'),
),
)->alwaysOpen(),
Entry::section(
'Advanced usage',
Entry::markdown(
'Logging all operations',
Path::of('advanced/logging.md'),
),
Entry::markdown(
'Extensions',
Path::of('advanced/extensions.md'),
),
),
);
};
17 changes: 17 additions & 0 deletions documentation/advanced/extensions.md
@@ -0,0 +1,17 @@
# Extensions

The advantage of having all operating system's operations done through a single abstraction is that you can easily add behaviour on top of that.

## Debugger

The [`innmind/debug` library](https://github.com/innmind/debug) provides a decorator that will send all the operations of the operating system to your [profiler (`innmind/profiler`)](https://github.com/innmind/profiler).

This debugger works for both http requests and cli applications.

**Note**: you can either add this debugger yourself or you can use [`innmind/http-framework`](https://github.com/innmind/httpframework) and [`innmind/cli-framework`](https://github.com/innmind/cliframework) that will automatically enable this debugger when there is a dsn provided in the `PROFILER` environment variable.

## Silent Cartographer

The [`innmind/silent-cartographer`](https://github.com/Innmind/SilentCartographer) is a CLI tool that will display all operating system's operations from all the PHP processes using this extension. This is useful when you want a glance at what's going on on your machine without the need to go through all the log files (if there's any).

**Note**: if you use [`innmind/http-framework`](https://github.com/innmind/httpframework) and [`innmind/cli-framework`](https://github.com/innmind/cliframework) this extension is automatically enabled.
17 changes: 17 additions & 0 deletions documentation/advanced/logging.md
@@ -0,0 +1,17 @@
# Logging all operations

If you want to trace everything that is done on your operating system you can use the logger decorator that will automatically write to your log file (almost) all operations.

**Note**: data and actions done on a socket are not logged as well as processes output to prevent logging too many data (at least for now).

```php
use Innmind\OperatingSystem\OperatingSystem\Logger;
use Psr\Log\LoggerInterface;

$os = new Logger(
$os,
/* any instance of LoggerInterface */
);
```

Now operations done with the new `$os` object will be written to your logs.
29 changes: 29 additions & 0 deletions documentation/readme.md
@@ -0,0 +1,29 @@
# Getting started

This library is here to help abstract all the operations that involve the operating system the PHP code run on.

This kind of abstraction is useful to move away from implementation details and towards user intentions. By abstracting implementations details it becomes easier to port an application into a new environment and accomodate the differences at the implementation level without any change in the user intentions.

The other advantage to use higher level abstractions is to enable end user to build more complex applications by freeing them from thinking of low level details.

For concrete examples have a look at the use cases available in the sidebar.

**Note**: this library is a small overlay on top of a set of individual libraries that contain the concrete abstractions. So you can start using only a subset of abstractions in your code as a starting point.

## Installation

```sh
composer require innmind/operating-system
```

## Basic usage

```php
use Innmind\OperatingSystem\Factory;

$os = Factory::build();
```

There's nothing more to add to start using this abstraction. Head toward the use cases to understand all the things you can do with it.

**Note**: This library doesn't work on windows environments.
121 changes: 121 additions & 0 deletions documentation/use_cases/filesystem.md
@@ -0,0 +1,121 @@
# Filesystem

Like [time](time.md) the filesystem can be easily accessed with PHP builtin functions but is a source of implicits and state. To move away from these problems the filesystem here is considered as an immutable structure that you _mount_ in your code. This forces you explicit what directories you are accessing and always verify that the structure you want to manipulate is as you expect.

## Examples

### Doing a filesystem backup

```php
use Innmind\Filesystem\Adapter;
use Innmind\Url\Path;

$backup = function(Adapter $source, Adapter $target): void {
$source->all()->foreach(function($file) use ($target): void {
// $file can be either a concrete file or directory
$target->add($file);
});
};
$backup(
$os->filesystem()->mount(Path::of('/path/to/source/')),
$os->filesystem()->mount(Path::of('/path/to/target/')),
);
```

Here we copy all the files from a local directory to another, but the `backup` function isn't aware of the locality of filesystems meaning that the source or target could be swapped to use remote filesystem (such as [S3](https://github.com/innmind/s3)).

### Adding a file in a subdirectory

```php
use Innmind\Filesystem\{
Adapter,
File\File,
Directory\Directory,
Name,
};
use Innmind\Url\Path;
use Innmind\Stream\Readable\Stream;

$addUserPicture = function(Adapter $filesystem, string $userId, File $picture): void {
if ($filesystem->contains(new Name($userId))) {
$userDirectory = $filesystem->get(new Name($userId));
} else {
$userDirectory = Directory::named($userId);
}

$filesystem->add($userDirectory->add($picture));
};
$addUserPicture(
$os->filesystem()->mount(Path::of('/path/to/users/data/')),
'some-unique-id',
File::named(
'picture.png',
Stream::open(
Path::of($_FILES['some_file']['tmp_name']),
),
),
);
```

Here you are forced to explicit the creation of the user directory instead of implicitly assuming it has been created by a previous process.

Once again the function is unaware where the file comes from or where it will be stored and simply how the process of adding the picture works.

### Checking if a file exists

Sometimes you don't want to mount a filesystem in order to know if a file or directory exist as you only want to check for their existence.

Example of checking if a `maintenance.lock` exist to prevent your webapp from running:

```php
use Innmind\Url\Path;

if ($os->filesystem()->contains(Path::of('/path/to/project/maintenance.lock'))) {
throw new \RuntimeException('Application still in maintenance');
}

// run normal webapp here
```

Or you could check the existence of a directory that is required for another sub process to run correctly:

```php
use Innmind\Url\Path;

if (!$os->filesystem()->contains(Path::of('/path/to/some/required/folder/'))) {
$os->control()->processes()->execute($mkdirCommand);
}

$os->control()->processes()->execute($subProcessCommand);
```

See [processes](processes.md) section on how to execute commands on your operating system.

### Mounting the `tmp` folder

Sometimes you want to use the `tmp` folder to write down files such as cache that can be saefly lost in case of a system reboot. You can easily mount this folder as any other folder like so:

```php
use Innmind\Filesystem\Adapter;

$tmp = $os->filesystem()->mount($os->status()->tmp());
$tmp instanceof Adapter; // true
```

It is a great way to forget about where the tmp folder is located and simply focus on what you need to do. And since you can use it as any other mounted filesystem you can change it for tests purposes.

### Watching for changes on the filesystem

A pattern we don't see much in PHP is an infinite loop to react to an event to perform another task. Here can build such pattern by watching for changes in a file or a directory.

```php
$runTests = $os->filesystem()->watch(Path::of('/path/to/project/src/'));

$runTests(function() use ($os): void {
$os->control()->processes()->execute($phpunitCommand);
});
```

Here it will run phpunit tests every time the `src/` folder changes. Concrete examples of this pattern can be found in [`innmind/lab-station`](https://github.com/Innmind/LabStation/blob/develop/src/Agent/WatchSources.php#L38) to run a suite of tools when sources change or in [`halsey/journal`](https://github.com/halsey-php/journal/blob/develop/src/Command/Preview.php#L58) to rebuild the website when the markdown files change.

**Important**: since there is not builtin way to watch for changes in a directory it checks the directory every second, so use it with care. Watching an individual file is a bit safer as it uses the `tail` command so there is no `sleep()` used.
60 changes: 60 additions & 0 deletions documentation/use_cases/http.md
@@ -0,0 +1,60 @@
# HTTP Client

## Calling an HTTP server

```php
use Innmind\Http\{
Message\Request\Request,
Message\Method,
Message\Response,
ProtocolVersion,
};
use Innmind\Url\Url;

$http = $os->remote()->http();

$response = $http(new Request(
Url::of('https://github.com'),
Method::get(),
new ProtocolVersion(2, 0),
));
$response instanceof Response; // true
```

All elements of a request/response call is built using objects to enforce correctness of the formatted messages.

**Note**: since request and responses messages can be viewed either from a client or a server the model is abstracted in the standalone [`innmind/http` library](https://github.com/innmind/http).

## Resiliency in a distributed system

One of the first things taught when working with distributed systems is that they will intermittently fail. To prevent your app to crash for an occasional failure a common pattern is the _retry pattern_ with a backoff strategy allowing the client to retry safe requests a certain amount of time before giving up. You can use this pattern like so:

```php
use Innmind\OperatingSystem\OperatingSystem\Resilient;
use Innmind\HttpTransport\ExponentialBackoffTransport;

$os = new Resilient($os);
$http = $os->remote()->http();
$http instanceof ExponentialBackoffTransport; // true
```

Another strategy you can add on top of that is the [circuit breaker pattern](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) that will stop sending request to a server known to have failed.

```php
use Innmind\HttpTransport\CircuitBreakerTransport;
use Innmind\TimeContinuum\Earth\Period\Minute;

$http = new CircuitBreakerTransport(
$http,
$os->clock(),
new Minute(1),
);
$request = new Request(/* ...args */)
$response = $http($request);
// if the previous call failed then the next call wil not even be sent to the
// server and the client will respond immediately unless 1 minute has elapsed
// between the 2 calls
$response = $http($request);
```

**Note**: the circuit breaker works on host per host basis meaning if `server1.com` fails then calls to `server2.com` will still be sent.
46 changes: 46 additions & 0 deletions documentation/use_cases/ipc.md
@@ -0,0 +1,46 @@
# Inter Process Communication (IPC)

To communicate between processes on a same system there is 2 approaches: sharing memory or passing messages through a socket.

The later is the safest of the two (but not exempt of problems) and you will find here the building blocks to communicate via a socket.

**Note**: the adage `share state through messages and not messages through state` is a pillar of the [actor model](https://en.wikipedia.org/wiki/Actor_model) and [initially of object oriented programming](https://www.youtube.com/watch?v=7erJ1DV_Tlo).

```php
# process acting a server
use Innmind\Socket\Address\Unix as Address;
use Innmind\TimeContinuum\Earth\ElapsedPeriod;
use Innmind\Immutable\Str;

$server = $os->sockets()->open(Address::of('/tmp/foo'));
$watch = $os->sockets()->watch(new ElapsedPeriod(1000))->forRead($server);

while (true) {
$ready = $watch();

if ($ready->toRead()->contains($server)) {
$client = $server->accept();
$client->write(Str::of('Hello 👋'));
$client->close();
}
}
```

```php
# process acting as client
use Innmind\Socket\Address\Unix as Address;
use Innmind\TimeContinuum\Earth\ElapsedPeriod;

$client = $os->sockets()->connectTo(Address::of('/tmp/foo'));
$watch = $os->sockets()->watch(new ElapsedPeriod(1000))->forRead($client);

do {
$ready = $watch();
} while (!$ready->toRead()->contains($client));

echo $client->read()->toString();
```

In the case the server is started first then the client would print `Hello 👋`.

**Important**: this is a very rough implementation of communication between processes. **DO NOT** use this simple implementation in your code, instead use a higher level API such as [`innmind/ipc`](https://github.com/innmind/ipc).

0 comments on commit c86fd6f

Please sign in to comment.