Skip to content

Commit

Permalink
Enhanced documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
joshmcrae committed Sep 8, 2024
1 parent 5a81962 commit da564d4
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 38 deletions.
40 changes: 24 additions & 16 deletions docs/dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ codebases. Instead of leaving it up to each class to create and configure its
dependencies, a service container _injects_ those dependencies into classes as
they are being instantiated.

To demonstrate what we're talking about, consider a class that needs a database
connection to do something useful.
To demonstrate this concept, consider a class that needs a database connection to
do something useful.

```php
<?php
Expand All @@ -19,7 +19,10 @@ class RegisterUser
{
$this
->db
->insert('users', func_get_args());
->insert('users', [
'username' => $username,
'pw_hash' => password_hash($password, PASSWORD_DEFAULT)
]);
}
}
```
Expand All @@ -32,8 +35,9 @@ throughout the application, and allows different configurations of the class
## Literals and Factories

In cases where Kick knows how to create all dependencies of a class, it will
automatically construct the class when requested. This is called auto-wiring,
and it enables quicker development while cutting down on configuration.
automatically construct the class when it needs to be resolved. This is called
auto-wiring, and it enables quicker development while cutting down on manual
configuration.

In some cases however, Kick cannot determine all dependencies. Take the `Database`
from the earlier example. We would typically require a host and some credentials
Expand All @@ -56,18 +60,20 @@ $container->literal('apiKey', getenv('API_KEY'));

A factory service definition on the other hand binds a service name to a `callable`
that is responsible for building the service on demand. This is useful in situations
where the service isn't required on every request and you want it created on demand.
where the service isn't required on every request and you want it constructed lazily.

```php
$container->factory(Database::class, fn () => new Database($container->resolve('dsn')));
```

Note that once resolved, an object created by a factory is cached and returned on
subsequent requests for that service.

## Providers

A Kick application can be passed a service provider, which is simply a `callable`
that is passed the container as an argument. A service provider can be used to
define services that cannot be automatically resolved and perform other initialization
required at application boot.
A Kick application can be configured with any number of service providers, tasked
with defining base depenendencies and performing other initialization during boot.
A service provider is simply a `callable` that receives an instance of `Kick\Service\Container`.

```php
<?php
Expand All @@ -76,8 +82,9 @@ use Kick\Service\Container;
use Kick\Application;

$app = (new Application)
->withProvider(fn (Container $c) =>
$c->literal(Database::class, new Database('sqlite::memory:'))
->withProvider(fn (Container $c) => $c
->literal('db_dsn', 'sqlite::memory:)
->factory(Database::class, fn () => new Database($c->resolve('db_dsn')))
);
```

Expand All @@ -87,14 +94,15 @@ To resolve a service, `Container::resolve()` can be called with the desired
service name. This should only be necessary within a service provider, because
Kick will automatically resolve dependencies when invoking route handlers and
middleware. Any type-hinted parameter specified on a route handler or middleware
`callable` will be auto-injected by the service container.
closure will be auto-injected by the service container.

```php

<?php

return fn (Database $db) =>
use Kick\Http\Request;

return fn (Request $request, Database $db) =>
$db
->query('select * from posts')
->query('select * from posts where uid = ?', $request->get('uid'))
->fetchAll();
```
8 changes: 4 additions & 4 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ use Kick\Http\Request;
## Defining a route

In our example configuration, files found under the `pages/` directory will map
to URIs that are understood by the application. A route file must return a `callable`
which accepts the incoming request as an argument and returns a response.
to URIs that are available in the application. A route file must return a closure
that accepts the incoming request as an argument and returns a response.

Let's create a file named `pages/greeting/_name.get.php`:

Expand All @@ -52,7 +52,7 @@ use Kick\View\Element as e;

return fn (Request $request) => e::html(
e::body(
e::h1('Hello, ', $request->get('name'), '!'),
e::h1('Hello, ', ucfirst($request->get('name')), '!'),
e::p('Welcome to Kick.')
)
);
Expand All @@ -71,5 +71,5 @@ command:
php -S 0.0.0.0:3000 -t public/
```

Navigate to `http://0.0.0.0:3000/greet/name` in the browser and replace `name`
Navigate to `http://0.0.0.0:3000/greeting/kick` in the browser and replace `kick`
with different values to get different greetings.
31 changes: 16 additions & 15 deletions docs/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ for an example.

## Naming conventions

The names of route files and paths can alter their behaviour ways:
The names of route files and paths will alter their behaviour in different ways:

- Files named `__middleware.php` specify middleware for the current and child
paths. Middleware is discussed later in this section.
- Files named `index.php` map to the resource `/` in a given path.
- Names prefixed with an underscore define dynamic URI segments. As an example,
`/posts/_pid/comments.php` would expand to the URI `/posts/:pid/comments`.
- Using a file extension prefixed with an HTTP method restricts that route to
Expand All @@ -23,8 +24,8 @@ by an HTTP `GET` request.

## Handlers

Route handlers are simply `callables` that take a `Request` object and return
a primitive PHP type, view or `Response` object. Whatever the response, the
Route handlers are simply closures that take a `Request` object and return
a primitive PHP type, `Element` or `Response` object. Whatever the response, the
application will automatically translate it into a `Request` with appropriate
`Content-Type` header. Route files must return a handler.

Expand All @@ -38,18 +39,18 @@ return fn (Request $request) => e::p('Hello, world!');
```

The `Request` object will be injected automatically, and any other type-hinted
parameter will also be injected by the service container. To get the value of a
parameters will also be injected by the service container. To get the value of a
dynamic path segment, the `Request::$segments` array can be used, or a call to
`Request::get()` with the segment name will return its value.

## Middleware

A `__middleware.php` file must return a `callable` or an array of container
service names that resolve to `callables`. Middleware are very similar to route
handlers, but in addition to a `Request` object they are also passed a `callable`
to be invoked before or after the middleware has done its work. This `callable`
executes the next middleware in the stack, returning the resulting `Response`,
or the route handler if all middleware have been invoked.
A `__middleware.php` file must return a closure or an array of container service
names that resolve to `callable`. Middleware are very similar to route handlers,
but in addition to a `Request` object they are also passed a `callable` to be
invoked before or after the middleware has done its work. This `callable` executes
the next middleware in the stack, or the route handler if all middleware have been
invoked, returning the resulting `Response`,

The following middleware uses a hypothetical `Auth` class to protect routes from
unauthorized users.
Expand All @@ -60,14 +61,14 @@ unauthorized users.
use Kick\Http\Request;

return function (Request $request, callable $next, Auth $auth) {
if (!$auth->hasActiveSession()) {
if (!$auth->hasActiveSession() && $request->path !== '/login') {
return new Response(302, ['location' => '/login']);
}

return $next($request);
};
```
Note that the result of the `$next` callable is returned.
Middleware can also modify responses by doing work after the callable is invoked.
Middleware encountered earlier in the path are executed before or "around" the
middleware encountered later in the path.

Note that the result of the `$next` callable is returned. Middleware can also modify
responses by doing work after the callable is invoked. Middleware encountered earlier
in the path are executed before, or "around", middleware encountered later in the path.
15 changes: 12 additions & 3 deletions docs/views.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Views

The `Kick\View\Element` class is responsible for generating HTML directly from
PHP code. Instead of calling out to a separate templating language, views can
be composed through the use of PHP functions, classes, loops and conditionals.
PHP code. Instead of calling out to a separate templating language, views are
composed through the use of PHP functions, classes, loops and conditionals.

```php
<?php
Expand Down Expand Up @@ -60,10 +60,15 @@ class Layout
e::script(src: 'https://cdn.com/library.js'),
e::title($title . ' - Acme Dashboard')
),
e::body(...$args)
e::body(
e::h1($title),
...$args
)
);
}
}

Layout::dashboard('User Profile', e::form(...));
```

```php
Expand All @@ -79,6 +84,8 @@ class Form
);
}
}

Form::input('email', 'Your email address...');
```

Functional programming can also help to compose views in an expressive way.
Expand All @@ -89,6 +96,8 @@ use Kick\Element\View as e;
$list = fn ($items) => e::ul(
array_map(($i) => e::li($i), $items)
);

$list(['apple', 'bannana', 'carrot']);
```

## Adding client-side functionality
Expand Down

0 comments on commit da564d4

Please sign in to comment.