zendframework / zend-expressive-hal Goto Github PK
View Code? Open in Web Editor NEWHypertext Application Language implementation for PHP and PSR-7
License: BSD 3-Clause "New" or "Revised" License
Hypertext Application Language implementation for PHP and PSR-7
License: BSD 3-Clause "New" or "Revised" License
XmlRenderer::createResourceElement() does not have support for PHPs DateTime object. As this works natively in json_encode() I feel like XmlRenderer also should have support for it to be consistent between the two.
Exception:
Encountered non-primitive type "DateTime" when serializing Zend\Expressive\Hal\Exception\HalResource instance; unable to serialize
I suspect that sometimes it would be nice to have the option to skip the check provided by the HalResource::compareResources
method.
This is because sometimes resources could have optional fields, or because an embedded resource makes sense only in certain cases
While trying to create a resource from a numbered array an exception is thrown due to empty($name)
check on zero index on this line.
$array = [
['foo' => 'bar'],
];
$resource->embed('foobar', $resourceGenerator->fromArray($array));
The resource should be generated just fine.
Exception is thrown: $name provided to Zend\Expressive\Hal\HalResource cannot be empty
I am trying to modify the default fields which appear in a collection response. In particular I'm filtering and paginating my collection and, beside _total_items
I would like to have also other fields like _total_items_without_filters
and _total_items_with filters_but_without_pagination
.
The fact is that inside RouteBasedCollectionStrategy
, the creation of the resource happens in the private
method extractIterator
which is called by the private method extractCollection
, which requires also the presence of the method extractPaginator
.
All of this said, to add a new field to the resource I must extends the RouteBasedCollectionStrategy
class, copy and paste all the above mentioned methods, just to edit the line $data = ['_total_items' => $count];
It would be nice if just the creation of the $data
could be exposed, at least in a protected method, so that modifying it could become easier
Creating a Link with a null
attribute, I get an InvalidArgumentException
. I'm not 100% sure about it but I guess that having null
attributes should be allowed in Hal links
new Link('a', 'b', false, ['c' => null]);
I would expect to get a Link
with a c
attribute with a null
value
An InvalidArgumentException
with message Zend\\Expressive\\Hal\\Link expects the $value to be a PHP primitive or array of strings; received NULL
is thrown
Provide a narrative description of what you are trying to accomplish.
There are typos on the documentation page https://docs.zendframework.com/zend-expressive-hal/
When paginating (eg. /todo?page=2) the "_page" is returned as string not int.
curl -XGET http://localhost:8080/todo?page=2
{
"_total_items": 30,
"_page": 2,
"_page_count": 2,
}
{
"_total_items": 30,
"_page": "2",
"_page_count": 2,
}
Writing a PR and will submit soon.
Provide a narrative description of what you are trying to accomplish.
When creating a HalResource, if one passes an array as $data with an empty array inside, this empty array is considered a embedded collection due to this line.
$halResource1 = new HalResource([
'id' => '1',
'roles' => [10, 11],
]);
$halResource2 = new HalResource([
'id' => '1',
'roles' => [],
]);
print_r($halResource1->toArray());
print_r($halResource2->toArray());
Array ( [roles] => Array ( [0] => 10 [1] => 11 ) [id] => 1 )
Array ( [roles] => Array ( ) [id] => 1 )
Array ( [roles] => Array ( [0] => 10 [1] => 11 ) [id] => 1 )
Array ( [id] => 1 [_embedded] => Array ( [roles] => Array ( ) ) )
Probably, we show check if it's an empty array on this line:
if (! is_array($value) || empty($value)) {
return false;
}
But this will prevent a real hal collection with an empty list (eg. search users rest api with name 'Silva' when none was found), unless you explicitly pass the $embedded constructor parameter, not embeded in the $data.
Thoughts?
It's not an issue but, with the current implementation, if you request page >2 (?page=2) and the collection has fewer pages, the generator returns the last valid page.
This behavior is intended or should we throw an OutOfBoundsException (or something like that)?
Bad return type for Zend\Expressive\Hal\Metadata\RouteBasedResourceMetadata::setRouteParams() method.
$metadataMap = $this->resourceGenerator->getMetadataMap();
$metadata = $metadataMap->get(Item::class);
$routesParams = array_merge($metadata->getRouteParams(), ['categoryId' => $categoryId]);
$metadata->setRouteParams($routesParams);
No error
throws TypeError :
Return value of Zend\\Expressive\\Hal\\Metadata\\RouteBasedResourceMetadata::setRouteParams() must be of the type array, none returned
Provide a narrative description of what you are trying to accomplish.
User.php
namespace App;
class User
{
/**
* @var string
*/
protected $id;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $email;
/**
* @var Avatar
*/
protected $avatar;
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): User
{
$this->name = $name;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(?string $email): User
{
$this->email = $email;
return $this;
}
public function getAvatar(): ?Avatar
{
return $this->avatar;
}
public function setAvatar(?Avatar $avatar): User
{
$this->avatar = $avatar;
return $this;
}
}
Avatar.php
namespace App;
class Avatar
{
/**
* @var string|null
*/
protected $id;
/**
* @var string|null
*/
protected $url;
public function getId(): ?string
{
return $this->id;
}
public function setId(?string $id): User
{
$this->id = $id;
return $this;
}
public function getUrl(): ?string
{
return $this->url;
}
public function setUrl(?string $url): User
{
$this->url = $url;
return $this;
}
}
config.php
MetadataMap::class => [
[
'__class__' => RouteBasedResourceMetadata::class,
'resource_class' => App\User::class,
'route' => 'api.user',
'extractor' => ClassMethodsHydrator::class,
],
[
'__class__' => RouteBasedResourceMetadata::class,
'resource_class' => App\Avatar::class,
'route' => 'api.avatar',
'extractor' => ClassMethodsHydrator::class,
],
],
$renderer = new JsonRenderer();
$avatar = new Avatar();
$avatar->setId('1234');
$avatar->setUrl('https://superfastcdn.com/myavatar.png');
$user = new User();
$user->setId('5678');
$user->setName('John Doe');
$user->setEmail('[email protected]');
$user->setAvatar($avatar);
$resource = $resourceGenerator->fromObject($user, $request);
echo $renderer->render($resource);
$user->setAvatar(null);
$resource = $resourceGenerator->fromObject($user, $request);
echo $renderer->render($resource);
{
"name": "John Doe",
"email": "[email protected]",
"_embedded": {
"avatar": {
"id": "1234",
"url": "https://superfastcdn.com/myavatar.png",
"_links": {
"self": {
"href": "https://api.acme.com/avatars/1234"
}
}
}
},
"_links": {
"self": {
"href": "https://api.acme.com/users/5678"
}
}
}
{
"name": "John Doe",
"email": "[email protected]",
"_embedded": {
"avatar": null
},
"_links": {
"self": {
"href": "https://api.acme.com/users/5678"
}
}
}
{
"name": "John Doe",
"email": "[email protected]",
"_embedded": {
"avatar": {
"id": "1234",
"url": "https://superfastcdn.com/myavatar.png",
"_links": {
"self": {
"href": "https://api.acme.com/avatars/1234"
}
}
}
},
"_links": {
"self": {
"href": "https://api.acme.com/users/5678"
}
}
}
{
"name": "John Doe",
"email": "[email protected]",
"avatar": null,
"_links": {
"self": {
"href": "https://api.acme.com/users/5678"
}
}
}
Provide a narrative description of what you are trying to accomplish.
MetadataMap::class => [
[
'__class__' => RouteBasedResourceMetadata::class,
'resource_class' => Post::class,
'route' => 'blog.post',
'extractor' => ClassMethods::class,
],
new RouteBasedResourceMetadata(
Post::class,
'blog.post',
ClassMethods::class
),
];
Imho both of the above should work the same. The later is better as it's typed.
The second config fails as only arrays are allowed.
Provide a narrative description of what you are trying to accomplish.
As already explained in the zend-expressive-hal post
I'm trying to generate a templated Link as defined by the PSR-13.
This is my actual routing configuration in the config/routes.php file:
$app->get('/api/test/{id}',App\Handler\TestHandler::class,'api.test.get');
The code I use to generate the link:
/* Code */
$linkGenerator=$this->resourceGenerator
->getLinkGenerator();
$linkGenerator->templatedFromRoute(
'test',
$request,
'api.test.get'
);
/* Code */
The json response that I expect is something similar to this:
{
"id": 1,
"name": "Test",
"_links": {
"test": {
"href": "http://localhost/expressive-test/public/api/test/{id}",
"templated": true
}
}
}
This is the error that I obtain:
Zend\Expressive\Router\Exception\RuntimeException raised in file D:\Sites\expressive-test\vendor\zendframework\zend-expressive-fastroute\src\FastRouteRouter.php line 299:
Message: Route `api.test.get` expects at least parameter values for [id], but received []
Trying to extract a collection of objects where one contains a NULL value, works fine for JSON, but when trying to output XML generates:
Encountered non-primitive type "NULL" when serializing Zend\Expressive\Hal\HalResource instance; unable to serialize
ConfigProvider.php
namespace App;
class ConfigProvider
{
/**
* Get configuration
*
* @return array
*/
public function __invoke(): array
{
return [
'dependencies' => [
'factories' => [
TestPageHandler::class => TestPageHandlerFactory::class,
],
],
MetadataMap::class => [
[
'__class__' => RouteBasedResourceMetadata::class,
'resource_class' => TestEntity::class,
'route' => 'test',
'extractor' => Reflection::class,
],
[
'__class__' => RouteBasedCollectionMetadata::class,
'collection_class' => TestCollection::class,
'collection_relation' => 'test',
'route' => 'tests',
],
],
'routes' => [
'tests' => [
'path' => '/test',
'middleware' => TestPageHandler::class,
'allowed_methods' => ['GET'],
],
'test' => [
'path' => '/test/:id',
'middleware' => TestPageHandler::class,
'allowed_methods' => ['GET'],
],
],
];
}
}
TestEntity.php
namespace App;
class TestEntity
{
/**
* @var int
*/
protected $id;
/**
* @var string
*/
protected $name;
/**
* @var string|null
*/
protected $description;
/**
* Get id
*
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* Set id
*
* @param int $id
*
* @return void
*/
public function setId(int $id): void
{
$this->id = $id;
}
/**
* Get name
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* Set name
*
* @param string $name
*
* @return void
*/
public function setName(string $name): void
{
$this->name = $name;
}
/**
* Get description
*
* @return null|string
*/
public function getDescription(): ?string
{
return $this->description;
}
/**
* Set description
*
* @param null|string $description
*
* @return void
*/
public function setDescription(?string $description): void
{
$this->description = $description;
}
}
TestPageCollection.php
namespace App;
use Zend\Paginator\Paginator;
class TestCollection extends Paginator
{
}
TestPageHandler.php
namespace App;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Expressive\Hal\HalResponseFactory;
use Zend\Expressive\Hal\ResourceGenerator;
use Zend\Paginator\Adapter\ArrayAdapter;
class TestPageHandler implements RequestHandlerInterface
{
/**
* @var ResourceGenerator
*/
protected $resourceGenerator;
/**
* @var HalResponseFactory
*/
protected $responseFactory;
/**
* @var TestEntity[]
*/
protected $testEntities;
/**
* Constructor
*
* @param ResourceGenerator $resourceGenerator
* @param HalResponseFactory $responseFactory
*/
public function __construct(ResourceGenerator $resourceGenerator, HalResponseFactory $responseFactory)
{
$this->resourceGenerator = $resourceGenerator;
$this->responseFactory = $responseFactory;
}
/**
* @inheritdoc
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$this->generateTestEntities();
$id = $request->getAttribute('id', false);
if ($id) {
$resource = $this->getTestEntity($id);
} else {
$resource = $this->getAllTestEntities();
}
return $this->responseFactory->createResponse($request,
$this->resourceGenerator->fromObject($resource, $request));
}
/**
* Get test entity
*
* @param $id
*
* @return TestEntity
*/
public function getTestEntity(int $id): TestEntity
{
return $this->testEntities[$id];
}
/**
* Get all test entities
*
* @return TestCollection
*/
public function getAllTestEntities(): TestCollection
{
return new TestCollection(new ArrayAdapter($this->testEntities));
}
/**
* Generate test entities
*
* @return void
*/
public function generateTestEntities(): void
{
$this->testEntities = [];
foreach (range(1, 10) as $id) {
$entity = new TestEntity;
$entity->setId($id);
$entity->setName('test' . $id);
$this->testEntities[$id] = $entity;
}
}
}
TestPageHandlerFactory.php
namespace App;
use Interop\Container\ContainerInterface;
use Zend\Expressive\Hal\HalResponseFactory;
use Zend\Expressive\Hal\ResourceGenerator;
use Zend\ServiceManager\Factory\FactoryInterface;
class TestPageHandlerFactory implements FactoryInterface
{
/**
* @inheritdoc
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null): TestPageHandler
{
return new $requestedName(
$container->get(ResourceGenerator::class),
$container->get(HalResponseFactory::class)
);
}
}
Valid JSON representing the collection when doing a GET on /test using JSON content negotiation.
Valid XML representing the collection when doing a GET on /test using XML content negotiation.
Valid JSON representing the collection when doing a GET on /test using JSON content negotiation.
{
"_total_items": 10,
"_page": 1,
"_page_count": 1,
"_links": {
"self": {
"href": "http://localhost/test?page=1"
}
},
"_embedded": {
"test": [
{
"id": 1,
"name": "test1",
"description": null,
"_links": {
"self": {
"href": "http://localhost/test/1"
}
}
},
{
"id": 2,
"name": "test2",
"description": null,
"_links": {
"self": {
"href": "http://localhost/test/2"
}
}
},
{
"id": 3,
"name": "test3",
"description": null,
"_links": {
"self": {
"href": "http://localhost/test/3"
}
}
},
{
"id": 4,
"name": "test4",
"description": null,
"_links": {
"self": {
"href": "http://localhost/test/4"
}
}
},
{
"id": 5,
"name": "test5",
"description": null,
"_links": {
"self": {
"href": "http://localhost/test/5"
}
}
},
{
"id": 6,
"name": "test6",
"description": null,
"_links": {
"self": {
"href": "http://localhost/test/6"
}
}
},
{
"id": 7,
"name": "test7",
"description": null,
"_links": {
"self": {
"href": "http://localhost/test/7"
}
}
},
{
"id": 8,
"name": "test8",
"description": null,
"_links": {
"self": {
"href": "http://localhost/test/8"
}
}
},
{
"id": 9,
"name": "test9",
"description": null,
"_links": {
"self": {
"href": "http://localhost/test/9"
}
}
},
{
"id": 10,
"name": "test10",
"description": null,
"_links": {
"self": {
"href": "http://localhost/test/10"
}
}
}
]
}
}
When doing a GET on /test using XML content negotiation, the following error is thrown:
Zend\Expressive\Hal\Exception\InvalidResourceValueException thrown with message "Encountered non-primitive type "NULL" when serializing Zend\Expressive\Hal\HalResource instance; unable to serialize"
Stacktrace:
#42 Zend\Expressive\Hal\Exception\InvalidResourceValueException in /var/www/vendor/zendframework/zend-expressive-hal/src/Exception/InvalidResourceValueException.php:22
#41 Zend\Expressive\Hal\Exception\InvalidResourceValueException:fromValue in /var/www/vendor/zendframework/zend-expressive-hal/src/Renderer/XmlRenderer.php:129
#40 Zend\Expressive\Hal\Renderer\XmlRenderer:createResourceElement in /var/www/vendor/zendframework/zend-expressive-hal/src/Renderer/XmlRenderer.php:146
#39 Zend\Expressive\Hal\Renderer\XmlRenderer:createNodeTree in /var/www/vendor/zendframework/zend-expressive-hal/src/Renderer/XmlRenderer.php:78
#38 Zend\Expressive\Hal\Renderer\XmlRenderer:createResourceNode in /var/www/vendor/zendframework/zend-expressive-hal/src/Renderer/XmlRenderer.php:73
#37 Zend\Expressive\Hal\Renderer\XmlRenderer:createResourceNode in /var/www/vendor/zendframework/zend-expressive-hal/src/Renderer/XmlRenderer.php:29
#36 Zend\Expressive\Hal\Renderer\XmlRenderer:render in /var/www/vendor/zendframework/zend-expressive-hal/src/HalResponseFactory.php:78
#35 Zend\Expressive\Hal\HalResponseFactory:createResponse in /var/www/src/App/src/Handler/TestPageHandler.php:46
#34 App\Handler\TestPageHandler:handle in /var/www/vendor/zendframework/zend-stratigility/src/Middleware/RequestHandlerMiddleware.php:53
#33 Zend\Stratigility\Middleware\RequestHandlerMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#32 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-expressive-router/src/Route.php:100
#31 Zend\Expressive\Router\Route:process in /var/www/vendor/zendframework/zend-expressive-router/src/RouteResult.php:110
#30 Zend\Expressive\Router\RouteResult:process in /var/www/vendor/zendframework/zend-expressive-router/src/Middleware/DispatchMiddleware.php:35
#29 Zend\Expressive\Router\Middleware\DispatchMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#28 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#27 Zend\Stratigility\MiddlewarePipe:handle in /var/www/src/CcApi/src/RouterOptionsToAttributesMiddleware.php:32
#26 Cc\Api\RouterOptionsToAttributesMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#25 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#24 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-expressive-helpers/src/UrlHelperMiddleware.php:45
#23 Zend\Expressive\Helper\UrlHelperMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#22 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#21 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-expressive-router/src/Middleware/MethodNotAllowedMiddleware.php:51
#20 Zend\Expressive\Router\Middleware\MethodNotAllowedMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#19 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#18 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-expressive-router/src/Middleware/ImplicitOptionsMiddleware.php:70
#17 Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#16 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#15 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-expressive-router/src/Middleware/ImplicitHeadMiddleware.php:84
#14 Zend\Expressive\Router\Middleware\ImplicitHeadMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#13 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#12 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-expressive-router/src/Middleware/RouteMiddleware.php:54
#11 Zend\Expressive\Router\Middleware\RouteMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#10 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#9 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-expressive-helpers/src/ServerUrlMiddleware.php:37
#8 Zend\Expressive\Helper\ServerUrlMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#7 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#6 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-stratigility/src/Middleware/ErrorHandler.php:143
#5 Zend\Stratigility\Middleware\ErrorHandler:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#4 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#3 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-httphandlerrunner/src/RequestHandlerRunner.php:95
#2 Zend\HttpHandlerRunner\RequestHandlerRunner:run in /var/www/vendor/zendframework/zend-expressive/src/Application.php:81
#1 Zend\Expressive\Application:run in /var/www/public/index.php:21
#0 {closure} in /var/www/public/index.php:22
It's simple 404 but files does exists in docs folder.
edit: also these pages are 404 too.
generating-custom-resources
representations
factories
In our expressive application we use one route definition for a whole endpoint. HAL's metadata map does not recognize optional parameters in this setup and complains about URIs it can't create for paginated list views.
// PipelineAndRoutesDelegator.php
$app->route(
'/book[/:id]',
[
BookAction::class
],
[
'GET', 'POST', 'PUT', 'DELETE', 'PATCH'
],
'book'
);
// ConfigProvider.php
MetadataMap::class => [
[
'__class__' => RouteBasedResourceMetadata::class,
'resource_class' => Book::class,
'route' => 'book',
'extractor' => ClassMethods::class,
],
[
'__class__' => RouteBasedCollectionMetadata::class,
'collection_class' => BookCollection::class,
'collection_relation' => 'book',
'route' => 'book',
],
],
{
"_total_items": 245,
"_page": 1,
"_page_count": 25,
"_links": {
"self": {
"href": "http://localhost:8080/book?page=1"
}
},
{
"code": 0,
"message": "Missing parameter \"id\"",
"trace": "#0 /…/vendor/zendframework/zend-router/src/Http/Segment.php(328): Zend\\Router\\Http\\Segment->buildPath(Array, Array, true, true, Array)\n#1
…
{main}"
}
I temporarily resolved this by adding
'route_params' => [
'id' => 0,
]
to the defintition of the collection. But then the generated link looks like this. It's functional, but not pretty:
"_links": {
"self": {
"href": "http://localhost:8080/book/0?page=1"
}
},
(ignore; issue accidentally opened from command-line)
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.