diff --git a/.castor/docs/best-practices.md b/.castor/docs/best-practices.md index 9420178..b9bd482 100644 --- a/.castor/docs/best-practices.md +++ b/.castor/docs/best-practices.md @@ -1,2 +1,14 @@ Best Practices -============= \ No newline at end of file +============== + +## Pass `Context` as the first argument of the function or method + +## Don't overuse `Context\with_value` for related values + +## Use Enums as Keys + +## Do not store `Context` inside objects + +## Avoid immutable values inside `Context` + +## Do not store services inside the `Context` \ No newline at end of file diff --git a/.castor/docs/faq.md b/.castor/docs/faq.md index e69de29..3b691c1 100644 --- a/.castor/docs/faq.md +++ b/.castor/docs/faq.md @@ -0,0 +1,12 @@ +Frequently Asked Questions +========================== + +## 1. Why no array context? + +For mainly two reasons. (1) I wanted an api that used composition to extend the context, and (2) I wanted an API that +let me use any type as key. Arrays only support integers and strings. + +## 2. Should I use it in my domain? + +Yes! It is designed for this very purpose. Once you add the `Context` interface as a type hint of a repository +method or some other function, a world of possibilities are opened in terms of evolvavility and extensibility. \ No newline at end of file diff --git a/.castor/docs/guides.md b/.castor/docs/guides.md index e69de29..43a9cac 100644 --- a/.castor/docs/guides.md +++ b/.castor/docs/guides.md @@ -0,0 +1,9 @@ +Guides & Examples +================= + +## Implementing Multi-Tenancy + +## Passing Logger Context + +## Template Params + diff --git a/.castor/docs/intro.md b/.castor/docs/intro.md index cce87db..bccfb8a 100644 --- a/.castor/docs/intro.md +++ b/.castor/docs/intro.md @@ -1,4 +1,5 @@ -# Introduction +Introduction +============ Context is an abstraction for passing request-scoped values down the call stack of an application. diff --git a/.castor/docs/why.md b/.castor/docs/why.md index e69de29..9dbfaf3 100644 --- a/.castor/docs/why.md +++ b/.castor/docs/why.md @@ -0,0 +1,83 @@ +Why this Library? +================= + +This library attempts to solve the so called context-passing problem. For a good overview of the problem you can +read [Matthias Noback's blog post about it](https://matthiasnoback.nl/2018/04/context-passing/), or continue +reading here. + +## Context Passing + +In traditional PHP applications, the *context* in which an action is being executed is a fundamental +piece of information to determine program logic. By context, we could mean, for example, the user that is +executing the action, or the current tenant. + +If you use the popular PHP frameworks, you can get that information pretty easily. It usually involves +calling a global (like the [Auth facade in Laravel][laravel-auth]) or injecting a service that stores this +information inside some shared class state (like [Symfony's Security service][symfony-auth]). + +The problem with this solution is that it relies on global shared state. Although their access mechanisms vary (one +uses global access while the other uses dependency injection), they still rely on storing state in a shared instance. +They are fundamentally the same thing. + +A better approach is to create a context object and pass it down explicitly down the call stack, so interested +parties can extract from it the information they need to process the request. But this object needs to be carefully +designed, so it does not expose a wide api and pollute client code making the interface too big (a violation of +the Dependency Inversion principle). + +The context package provides such object. It allows you to give every request its own immutable context, where you +can store request specific values. It is not a service, so it can't be shared globally, and it must be passed +explicitly down the call stack, so no magic mutations happen during its lifecycle. + +## The `Context` interface + +This package defines one very small and simple interface called `Castor\Context`. Your code should typehint to this +interface always. This library is intentionally simple and has a small api surface and footprint because it is +intended to be used even in your domain layer. + +As we said, the `Context` interface has an intentionally simple api, with just one method: `value(mixed $key): mixed`. +This method returns the value "stored" in the context for the given key. Pay special attention to the fact that +you can store values of any type as keys, and comparison of the keys is done by strict equality. + +If you are a good observer, you'll quickly realize the `Context` interface does not define any methods that mutate +its internal state like `set(mixed $key, mixed $value): void`. Although we considered that method for a second, we +copied from Go the idea to make this interface unavoidably immutable. You can only "store" new values by composing +a new `Context`, and we have provided some functions to ease that process. + +## The `Context\fallback()` and the `Context\with_value()` functions + +First, in order to "store" a value in the context, you need to get the fallback context. This is some sort of "empty" +context that always returns `null`. You call `Context\fallback()` to do this. + +```php +value('foo')); // Prints: NULL +``` + +Once you have a `Context` instance, you can "store" a value in it by calling `Context\with_value()`: + +```php +value('foo')); // Prints: string(3) "bar" +``` + +Basically, those two functions and a single interface is the whole api surface of the context +package. In the next chapter we'll go to some guides on how to leverage the power of the context api +to build powerful functionality in your applications. + +[laravel-auth]: https://laravel.com/docs/9.x/authentication#retrieving-the-authenticated-user +[symfony-auth]: https://symfony.com/doc/current/security.html#fetching-the-user-from-a-service \ No newline at end of file