spiral / docs Goto Github PK
View Code? Open in Web Editor NEWSpiral Framework and Components Documentation
Home Page: https://spiral.dev/docs
Spiral Framework and Components Documentation
Home Page: https://spiral.dev/docs
Shouldn't here be mentioned that the framework already has a bootloader with a binding for the ActorInterface
(Spiral\Bootloader\Auth\SecurityActorBootloader
)?
Incorrect bootloaders order in doc
https://spiral.dev/docs/extension-sentry
Sentry should go after Debug bootloaders
Maybe floating "scroll-up" button should remember the place when clicked and transform to "scroll-down" button which will scroll user back to the same place (like on vk.com, habrahabr.ru)?
In Usage with Singletons section, the paragraph:
You can not use
CookieQuery
as__construct
argument. Queue is only available within IoC context of CookieMiddleware and must be requested from container directly (use use method injection as showed above):
Does "Queue" here should be "Cookie"?
Plus give example of combination static analysis + polymorhic relation + on delete trigger
spiral/validation#12
DBAL now forces stuff into database. better approach but i have to update docs.
I'm a little confused to current documents' outline:
It seems that the section "Getting Started" is mainly introductions to the framework and some related strategies(versioning, contributing...), but there is an "action guide" mixed into it: installing.
In my opinion, installing should be a part of "how to make the project running", including "installing, configuration, trying it out, deployment". That is what "getting started" or "quick start" should do.
I think this should be better:
"Cookbook" and "Basics" are both "basic knowledge" to use this framework to develop applications. I suggest that "Basics" should be some "more basic" things for a simple web application, like "Routing, Controller, Views, Database, ORM, Handler, Security, Testing..." "Cookbook" is deeper, like "Console, Queue, Jobs, Internalization, Event Broadcasting, GRPC..."
There could be another section "Components", all official components' documents can be put there.
Some time ago we've used DataGrid with other data source.
I suppose you can review our example and prepare some example for page Components->Data Grid
I will prepare some example text but feel free to change it as you need.
If you want to use DatGrid with some other data source you can prepare some files and your grid will work with any other data. It can be some REST service or something else. You will need to prepare:
Let's make some data grid with products list received from REST service.
As an display part we will use Keeper.
As soon as Query object will be main transfer object it should be prepared next.
To be used as data grid it should be suitable by some conditions:
<?php
declare(strict_types=1);
namespace App\DTO\Product;
use App\Service\Product\ProductService;
class ProductsQuery implements \IteratorAggregate, \Countable
{
private const DEFAULT_OFFSET = 0;
private const DEFAULT_LIMIT = 25;
private array $filters = [];
private array $sorters = [];
public function __construct(
private readonly ProductService $products,
private int $offset = self::DEFAULT_OFFSET,
private int $limit = self::DEFAULT_LIMIT
) {
}
public function addFilter(string $field, mixed $value): static
{
$this->filters[$field] = $value;
return $this;
}
public function addSorter(string $field, string $desc): static
{
$this->sorters[$field] = $desc;
return $this;
}
public function getFilters(): array
{
return $this->filters;
}
public function getSorters(): array
{
return $this->sorters;
}
public function getLimit(): int
{
return $this->limit;
}
public function setLimit(int $limit): static
{
$this->limit = $limit;
return $this;
}
public function getOffset(): int
{
return $this->offset;
}
public function setOffset(int $offset): static
{
$this->offset = $offset;
return $this;
}
public function getIterator(): \Traversable
{
return $this->products->getList($this);
}
public function count(): int
{
return $this->products->countList($this);
}
}
As you can see it is some simple DTO with service usage.
We need to have some representation with some required fields for display. We will use some other DTO.
<?php
declare(strict_types=1);
namespace App\DTO\Product;
class Product implements \JsonSerializable
{
public array $properties = [];
public function __construct(public string $code, public string $title)
{
}
public function jsonSerialize(): mixed
{
return [
'code' => $this->code,
'title' => $this->title,
'properties' => $this->properties,
];
}
}
<?php
declare(strict_types=1);
namespace App\Service\Product;
use App\DTO\Product\ProductsQuery;
class ProductService
{
public function __construct(private readonly ProductRESTService $rest)
{
}
/**
* @param ProductsQuery $query
*
* @return \Traversable|Product[]
*/
public function getList(ProductsQuery $query): \Traversable
{
return $this->rest->list(
$query->getOffset(),
$query->getLimit(),
$this->prepareFilters($query),
$this->prepareSorters($query)
);
}
public function countList(ProductsQuery $query): int
{
return $this->rest->count($this->prepareFilters($query));
}
private function prepareFilters(ProductsQuery $query): iterable
{
// some implementation
}
private function prepareSorters(ProductsQuery $query): iterable
{
// some implementation
}
}
We need some specific class to register for our grid
This class will be responsible for applying filters/sorters/pagination from datagrid component to our data source
<?php
declare(strict_types=1);
namespace App\DataGrid\Writer;
use App\DTO\Product\Product;
use App\DTO\Product\ProductsQuery;
use Spiral\DataGrid\Compiler;
use Spiral\DataGrid\Specification\FilterInterface;
use Spiral\DataGrid\Specification\SorterInterface;
use Spiral\DataGrid\SpecificationInterface;
use Spiral\DataGrid\WriterInterface;
use Spiral\DataGrid\Specification;
class ProductWriter implements WriterInterface
{
protected const SORTER_DIRECTIONS = [
Specification\Sorter\AscSorter::class => 'ASC',
Specification\Sorter\DescSorter::class => 'DESC',
];
/**
* @param ProductsQuery|mixed $source
* @param SpecificationInterface $specification
* @param Compiler $compiler
*
* @return Product[]|null
*/
public function write($source, SpecificationInterface $specification, Compiler $compiler): ?ProductsQuery
{
return match (true) {
!$this->sourceAcceptable($source) => null,
$specification instanceof FilterInterface => $this->writeFilter($source, $specification),
$specification instanceof SorterInterface => $this->writeSorter($source, $specification),
$specification instanceof Specification\Pagination\Limit => $source
->setLimit((int)$specification->getValue()),
$specification instanceof Specification\Pagination\Offset => $source
->setOffset((int)$specification->getValue()),
default => null
};
}
protected function writeFilter(ProductsQuery $source, FilterInterface $filter): ?ProductsQuery
{
if ($filter instanceof Specification\Filter\Equals) {
return $source->addFilter($filter->getExpression(), $filter->getValue());
}
return null;
}
protected function writeSorter(ProductsQuery $source, SorterInterface $sorter): ?ProductsQuery
{
if ($sorter instanceof Specification\Sorter\AscSorter || $sorter instanceof Specification\Sorter\DescSorter) {
$direction = static::SORTER_DIRECTIONS[\get_class($sorter)];
foreach ($sorter->getExpressions() as $expression) {
$source = $source->addSorter($expression, $direction);
}
return $source;
}
return null;
}
/**
* @param mixed $source
*
* @return bool
*
* @psalm-suppress InternalMethod
*/
protected function sourceAcceptable(mixed $source): bool
{
return $source instanceof ProductsQuery;
}
}
For applying new writer we should register it for dataGrid component. Simplest way to do it - prepare config file app/config/dataGrid.php
:
<?php
declare(strict_types=1);
use Spiral\Cycle\DataGrid\Writer as CycleWriter;
use App\DataGrid\Writer\ProductWriter;
return [
'writers' => [
CycleWriter\QueryWriter::class,
...,
CycleWriter\BetweenWriter::class,
ProductWriter::class,
],
];
For work with new grid we need to describe our new grid:
<?php
declare(strict_types=1);
namespace App\View;
use App\DTO\Product\Product;
use Spiral\DataGrid\Specification\Filter;
use Spiral\DataGrid\Specification\Sorter;
use Spiral\DataGrid\Specification\Value;
class ProductGrid extends AbstractGrid
{
public function __construct()
{
$this->addFilter(
'search',
new Filter\Equals('title', new Value\StringValue())
);
$this->addSorter('title', new Sorter\Sorter('title'));
$this->addSorter('code', new Sorter\Sorter('code'));
$this->setDefaultPaginator();
}
public function __invoke(Product $product): array
{
return [
'code' => $product->code,
'title' => $product->title,
];
}
}
For work with our grid we need to register controller with actions for display and receiving list:
<?php
declare(strict_types=1);
namespace App\Controller\Keeper;
use App\DTO\Product\ProductsQuery;
use App\Service\Product\ProductService;
use App\Service\Sanitizer\HttpSanitizer;
use App\View\ProductGrid;
use Psr\Http\Message\ResponseInterface;
use Spiral\Auth\AuthScope;
use Spiral\Cycle\DataGrid\Annotation\DataGrid;
use Spiral\Http\ResponseWrapper;
use Spiral\Keeper\Annotation as Keeper;
use Spiral\Views\ViewsInterface;
#[Keeper\Controller(name: 'products', prefix: '/products')]
class ProductController extends KeeperController
{
public function __construct(
ViewsInterface $views,
AuthScope $userAuth,
ResponseWrapper $responseWrapper,
HttpSanitizer $sanitizer,
private readonly ProductService $productService
) {
parent::__construct($views, $userAuth, $responseWrapper, $sanitizer);
}
#[Keeper\Action(route: '', methods: 'GET')]
#[Keeper\Sitemap\Link(title: 'Products', options: ['icon' => 'box'], position: 6.0)]
public function index(): ResponseInterface
{
return $this->responseWrapper->html(
$this->views->render('keeper/product/list')
);
}
#[Keeper\Action(route: '/list', methods: 'GET')]
#[DataGrid(grid: ProductGrid::class)]
public function list(): ProductsQuery
{
return new ProductsQuery($this->productService);
}
}
To display new grid we need to describe it as a template:
<extends:keeper:layout.page title="Products"/>
<use:bundle path="keeper:bundle"/>
<block:content>
<ui:grid url="@action('products.list', inject('params', []))">
<grid:filter search="true" immediate="300"/>
<grid:cell.text name="code" label="Code" sort="true"/>
<grid:cell.text name="title" label="Title" sort="true">
</ui:grid>
</block:content>
Currently there's no details of Broadcasts, Go services and job integration and it's difficult to implement them accordingly. It would be better there's the documentation
During the investigation of docs for checker I've detected some possible ways to improve it:
Controller actions can have only string arguments
Add an example how to fi it spiral/framework#773
I think specifying Support policy might be a good idea, cause many companies take this into account when choosing new technology. Right now there's only feature development roadmap here, support and security fixes can be added too
Examples:
https://laravel.com/docs/6.x/releases#support-policy
https://symfony.com/releases#checker
https://symfony.com/doc/current/contributing/community/releases.html
Related PR: spiral/framework#438
At https://spiral.dev/docs/packages-temporal-bridge/2.9/en you have described mostly positive scenarios for Temporal but what about exceptions handling?
If devs will skipp it it is possible to hit with the problem when workflow will try to execute some actions with error in unstoppable mode
It's not clear how to make cycle schemas work without annotations.
Having a bootloader or an example for plugging manual schema in spiral apps would be great.
Would be good to add section to authentication documentation about how to configure JWT authentication.
By default implementation of TokenStorageInterface are required, but framework provide only database
and seccion
token storage, which is mostly not need for JWT.
Please also add example of JwtTokenStorage
implementation.
I think it would be very helpful if everywhere where guide has "dump" or "echo" constructions to show some results in comments
echo 1; // 1
Add "NEXT PAGE" and "PREV PAGE" buttons in the header and in the footer of all guide pages
Related PR: spiral/framework#435
I meet the same issue as spiral/app#14 described.
This is the second time I come to the PostCommand step and meet the same error:
$ php app.php seed:post
[Cycle\ORM\Exception\ORMException]
Unable to find Entity role for repository App\Repository\UserRepository
in E:\projects\myapp\vendor\spiral\framework\src\Cycle\RepositoryInjector.php:56
The first time I do the quick start project on Mac OSX with PHP 7.3, the second time I do it on Windows 10.
I have pushed the codebase to Github.
I haven't changed my codes to the same as your demo, because there are many steps left. And it seems in spiral/app#14 the problem is not solved, yet. @itsursujit passed the first error when he runs seed:post
with PrototypeTrait
, He manually removed PrototypeTrait
, add dependencies and variables.
There is another issue with this step:
It does not cause any error, but IDE(PHPSorm) doesn't recognize the $this->users
property, because we don't add Prototyped
annotation to it, the doc doesn't say we should do it.
For example to explain how sample app got created
Include into video tutorials
Does anyone has good example? What should be covered in guide first?
Related PR: spiral/framework#396
Please add Note to documentation about this issue
I have rr
config below
jobs:
pool:
command: "php app.php"
# WE SAY - DO NOT CONSUME!!!
consume: []
pipelines:
memory:
driver: "memory"
config:
priority: 10
prefetch: 10000
notification:
driver: amqp
config:
priority: 10
prefetch: 100
queue: "notification"
and config/queue.php
config
return [
'connections' => [
'roadrunner' => [
'driver' => 'roadrunner',
'default' => 'local',
'pipelines' => [
'local' => [
'connector' => new MemoryCreateInfo('local'),
// WE SAY - CONSUME IT!!!
'consume' => true,
],
],
],
],
];
The queue config from php will redefine rr
consume config.
Actually rr
config will be like this
jobs:
pool:
command: "php app.php"
# WE SAY - CONSUME IT!!!
consume: ["local"]
pipelines:
memory:
driver: "memory"
config:
priority: 10
prefetch: 10000
notification:
driver: amqp
config: # NEW in 2.7
priority: 10
prefetch: 100
queue: "notification"
Had some fun today with the framework. Really cool. However I hit a snag when I followed this step:
Create main.go: Download default main.go file, we are going to use it later to register custom services. You can store this file in the root of your project or other location.
Source: https://spiral.dev/docs/framework-application-server
My issues:
vendor/
directory. (Composer and Go mod both try to use vendor/
)github.com/spiral/broadcast
was being downloaded as v1 instead of v2+ gave weird "not found" errors)Workaround(s):
golang/
. (I also put my RPC code in there.)main.go
and go.mod
Next, in go.mod
:
module github.com/spiral/framework
to module <your-github-skeleton-repo>
Next, to get everything working I did:
cd golang
go build
mv <your-github-skeleton-repo> ../spiral
cd ..
spiral -v -d
Thanks!
Adding websockets to an application by following the documentation had some pitfalls I'd like others to avoid:
The default GRPC and Web bundles include a pre-build component to gain access to the broadcast topic via WebSocket client. To activate the component, enable the
ws
section.rr.yaml
config file with the desired path:ws.path: "/ws"
This leaves me wondering - what other options for the WS components aside from path
are there? There is no documentation for it, neither in the Spiral nor the Roadrunner docs.
Usage sample to listen a specific channel
import { SFSocket } from '@spiralscout/websockets'; const socketOptions = { host: 'localhost' }; const ws = new SFSocket(socketOptions); SFSocket.ready(); // ... // disconnect everything ws.disconnect()
Using the code sample for the spiral websocket client, the connection fails due to an unexpected end of file error on the server:
DEBU[0009] [ws] 172.21.0.1:62314 websocket: close 1006 (abnormal closure): unexpected EOF
This, of course, is because the connection is immediately terminated to showcase closing connections, but I would suggest commenting it out or change the sample to something that works out of the box. The error is obvious once you know what it is caused by, but confusing if you're getting started.
Consume Topic
To consume topic using JavaScript client your must send command message in a form of {"topic":"join", "payload": ["topic-name"]}. Modify your script to automatically consume topic:// ...
If the recommended way of connecting is the Spiral websockets client, why do the following code samples continue the native example? I mean, it's not hard to rewrite the code to use the client, but it would be nice if the docs were consistent here.
The trailing backslash in the documentation is missing for the class:
use \PHPHtmlParser\Dom;
Which make an error occurs while following the documentation regarding the jobs and queues section.
When using use PHPHtmlParser\Dom;
an error occurs with class not found exception from PHP.
Success: Success Execution with Backslash
Error: Error Excecution without Backslash
Thanks
Closes #169
The following code
$selector = $this->find()
->with('owner', ['method' => RelationLoader::LEFT_JOIN])
->with('owner.user_data', ['alias' => 'user_data','method' => RelationLoader::LEFT_JOIN]);
Produce the following SQL:
FROM `integrations` AS `integration`
INNER JOIN `users` AS `integration_owner`
ON `integration_owner`.`id` = `integration`.`owner_id`
LEFT JOIN `user_datas` AS `user_data`
ON `user_data`.`user_id` = `integration_owner`.`id`
First join expected to be LEFT JOIN.
Could you provide more information about components and their properties with examples?
For example you have ui:date and there are properties format
, source-format
, title-format
, value
Which formats should be used in properties? js, php?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.