GithubHelp home page GithubHelp logo

docs's People

Contributors

andrewkirkovski avatar bjuri avatar butschster avatar cutcaster avatar daniellienert avatar divineniiquaye avatar eugenetrus avatar g4li avatar gam6itko avatar hannaharusava avatar imbue avatar jwdeitch avatar krwu avatar krzysztofrewak avatar markinigor avatar meekstellar avatar msmakouz avatar nekufa avatar nex-otaku avatar romanzaycev avatar roxblnfk avatar rustatian avatar samdark avatar serafimarts avatar siad007 avatar spiralscout avatar thomaswunderlich avatar twmbx avatar vvval avatar wolfy-j avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

docs's Issues

RBAC and Security

Shouldn't here be mentioned that the framework already has a bootloader with a binding for the ActorInterface (Spiral\Bootloader\Auth\SecurityActorBootloader)?

Scroll down button

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)?

Does "queue" here should be "Cookie"?

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"?

Outline adjustment

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:

  • Overview
    • About Spiral
    • Versioning
    • Changelog
    • Contributing
    • LICENSE
  • Getting Started
    • Installing
    • Directory Structure
    • Configuration
    • Trying it
    • Deployment
  • Basics
    ...

"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.

Example of DataGrid with data source different from Cycle Repository

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.

Basic description

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:

  1. Query object
    • object used for transfer sorters and filters to real data service
    • object used for transfer pagination conditions to real data service
  2. Writer class
  • used as writer for DataGrid component
  • preparing filters with keeping it in Query object
  • preparing sorters with keeping it in Query object
  • preparing pagination with keeping it in Query object
  1. Service with receiving and count data by Query object

Implementation

Required functionality

Let's make some data grid with products list received from REST service.
As an display part we will use Keeper.

Query objects

Query DTO

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:

  1. It should be iterable to work with data
  2. It should be countable to work with data pagination
<?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.

Product DTO

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,
        ];
    }
}

Service for interaction with some REST

<?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
    }
}

DataGrid writer

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;
    }
}

Writer registration

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,
    ],
];

DataGrid object

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,
        ];
    }
}

Controller

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);
    }
}

Stempler view

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>

Result view

image

Checkers docs improvements

During the investigation of docs for checker I've detected some possible ways to improve it:

  1. Checkers chapter now located in Security - it is not obvious. It will be more effective to place it in Filter/Request form. At least you can make a link in subchapter Filter Object
  2. It will be more comfortable to have a description in documentation how to implement new checker via ValidationBootloader. Now there are only description with validation config file and it is not the best way to add new checkers as I can see.

Manual schema bootloader example

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.

Needs documentation for JWT authentication

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.

Dump results

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

Navigation buttons

Add "NEXT PAGE" and "PREV PAGE" buttons in the header and in the footer of all guide pages

"seed:post" throw error when following quick start doc

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.

image

Docs. Note about rr queue `consume` config

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"

Feature request to update docs: Building Application Server

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:

  • Storing file in the root of the project broke vendor/ directory. (Composer and Go mod both try to use vendor/)
  • Compile errors. (github.com/spiral/broadcast was being downloaded as v1 instead of v2+ gave weird "not found" errors)

Workaround(s):

  • Create a separate sub-folder just for go. I used golang/. (I also put my RPC code in there.)
  • Download both main.go and go.mod

Next, in go.mod:

  • Change module github.com/spiral/framework to module <your-github-skeleton-repo>
  • Change the go version to your own

Next, to get everything working I did:

cd golang
go build
mv <your-github-skeleton-repo> ../spiral
cd ..
spiral -v -d

Thanks!

WebSockets guide hard to follow

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.

Double left join in record selector

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.

Description for all ui:{compoment}

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.