Create REST and GraphQL APIs, scaffold Jamstack webapps, stream changes in real-time.

Overview

API Platform

API Platform is a next-generation web framework designed to easily create API-first projects without compromising extensibility and flexibility:

GitHub Actions Codecov SymfonyInsight Scrutinizer Code Quality

The official project documentation is available on the API Platform website.

API Platform embraces open web standards (OpenAPI, RDF/JSON-LD/Hydra, GraphQL, JSON:API, HAL, OAuth...) and the Linked Data movement. Your API will automatically expose structured data in Schema.org / JSON-LD. It means that your API Platform application is usable out of the box with technologies of the semantic web.

It also means that your SEO will be improved because Google leverages these formats.

Last but not least, the server component of API Platform is built on top of the Symfony framework, while client components leverage React (a Vue.js flavor is also available). It means that you can:

  • Use thousands of Symfony bundles and React components with API Platform.
  • Integrate API Platform in any existing Symfony or React application.
  • Reuse all your Symfony and React skills, benefit of the incredible amount of documentation available.
  • Enjoy the popular Doctrine ORM (used by default, but fully optional: you can use the data provider you want, including but not limited to MongoDB and Elasticsearch)

Install

Read the official "Getting Started" guide.

Credits

Created by Kévin Dunglas. Commercial support available at Les-Tilleuls.coop.

Comments
  • Loading API gives 502 bad gateway

    Loading API gives 502 bad gateway

    HI All,

    When i load the API using http://localhost:8080/ URL it gives me "502 Bad Gateway". Could anyone please guide me what is the issue and how to fix this.

    Thanks in Advance.

    question waiting 
    opened by krishnathokala 52
  • Cannot start docker services

    Cannot start docker services

    Hello.

    I'm struggling with docker in AP 2.2.0. When I run docker-compose up -d, everythings seems to work.

    docker-compose up -d --build
    
    ...
    
    Successfully built d04505723c6d
    Creating apiplatform220_admin_1       ... done
    Creating apiplatform220_php_1         ... done
    Creating apiplatform220_client_1      ... done
    Creating apiplatform220_api_1         ... done
    Creating apiplatform220_admin_1       ...
    Creating apiplatform220_cache-proxy_1 ... done
    Creating apiplatform220_cache-proxy_1 ...
    Creating apiplatform220_h2-proxy_1    ... done
    

    But when I try to open https://localhost I'm having an exception ERR_CONNECTION_REFUSED.

    My logs :

    docker-compose logs -f
    Attaching to apiplatform220_h2-proxy_1, apiplatform220_cache-proxy_1, apiplatform220_api_1, apiplatform220_admin_1, apiplatform220_client_1, apiplatform220_php_1, apiplatform220_db_1
    h2-proxy_1     | 2018/02/16 21:26:45 [emerg] 1#1: host not found in upstream "client" in /etc/nginx/conf.d/default.conf:9
    api_1          | 2018/02/16 21:26:19 [emerg] 1#1: host not found in upstream "php" in /etc/nginx/conf.d/default.conf:11
    h2-proxy_1     | nginx: [emerg] host not found in upstream "client" in /etc/nginx/conf.d/default.conf:9
    api_1          | nginx: [emerg] host not found in upstream "php" in /etc/nginx/conf.d/default.conf:11
    cache-proxy_1  | + varnishd -a :80 -f /etc/varnish/default.vcl -s malloc,256m
    php_1          | Composer could not find a composer.json file in /srv/api
    admin_1        | yarn run v1.3.2
    client_1       | yarn run v1.3.2
    cache-proxy_1  | Error: Cannot read -f file '/etc/varnish/default.vcl' (No such file or directory)
    php_1          | To initialize a project, please create a composer.json file as described in the https://getcomposer.org/ "Getting Started" section
    admin_1        | info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
    db_1           | LOG:  database system was shut down at 2018-02-16 21:07:03 UTC
    client_1       | error Couldn't find a package.json file in "/usr/src/client"
    cache-proxy_1  | (-? gives usage)
    admin_1        | error Couldn't find a package.json file in "/usr/src/admin"
    db_1           | LOG:  MultiXact member wraparound protections are now enabled
    client_1       | info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
    db_1           | LOG:  database system is ready to accept connections
    db_1           | LOG:  autovacuum launcher started
    db_1           | LOG:  database system was interrupted; last known up at 2018-02-16 21:08:05 UTC
    db_1           | LOG:  database system was not properly shut down; automatic recovery in progress
    db_1           | LOG:  invalid record length at 0/14EE8E0: wanted 24, got 0
    db_1           | LOG:  redo is not required
    db_1           | LOG:  MultiXact member wraparound protections are now enabled
    db_1           | LOG:  database system is ready to accept connections
    db_1           | LOG:  autovacuum launcher started
    apiplatform220_h2-proxy_1 exited with code 1
    apiplatform220_api_1 exited with code 1
    apiplatform220_client_1 exited with code 1
    apiplatform220_php_1 exited with code 1
    apiplatform220_cache-proxy_1 exited with code 255
    apiplatform220_admin_1 exited with code 1
    

    Anyone can help me ?

    Additional notes

    OS : Windows

    docker -v
    Docker version 17.12.0-ce, build c97c6d6
    
    docker-compose ps
                Name                          Command               State             Ports
    ------------------------------------------------------------------------------------------------
    apiplatform220_admin_1         /bin/sh -c yarn start           Exit 1
    apiplatform220_api_1           nginx -g daemon off;            Up         0.0.0.0:8080->80/tcp
    apiplatform220_cache-proxy_1   docker-app-start                Exit 255
    apiplatform220_client_1        /bin/sh -c yarn start           Exit 1
    apiplatform220_db_1            docker-entrypoint.sh postgres   Up         0.0.0.0:5433->5432/tcp
    apiplatform220_h2-proxy_1      nginx -g daemon off;            Exit 1
    apiplatform220_php_1           docker-entrypoint php-fpm       Exit 1
    
    question 
    opened by jon-ht 37
  • Not able to send ID in POST method

    Not able to send ID in POST method

    I would like to send generated ID from UI to API but right now it is not possible. API is trying to do update, response is "Method is not allowed" So right now i'm sending it as uuid and then in the event doing extraction from request setting to entity.

    It will be great to allow send ID param to POST and to have "optional" parameter for writing. What do you think?

    question wontfix 
    opened by akadlec 27
  • ApiResource parameters denormalizationContext and normalizationContext doesn't affect

    ApiResource parameters denormalizationContext and normalizationContext doesn't affect

    API Platform version(s) affected: 2.6.4 Everything ok when you downgrade to 2.6.3

    Description
    ApiResource parameters denormalizationContext and normalizationContext doesn't affect.

    How to reproduce
    In version 2.6.4 - swagger shows all fields and ignores denormalizationContext and normalizationContext But In versions before that, for example 2.6.3 - everything is ok. Swagger shows considering denormalizationContext and normalizationContext goups

    How swagger looks with ApiPlatform version 2.6.3 image

    Same code with 2.6.4 image

    Original file is there: https://github.com/kadirov/api-starter-kit/blob/master/src/Entity/User.php

    
    #[ApiResource(
        collectionOperations: [
            'get'                => [
                'security'              => "is_granted('ROLE_ADMIN')",
                'normalization_context' => ['groups' => ['users:read']],
            ],
            'post'               => [
                'controller' => UserCreateAction::class,
            ],
            'aboutMe'            => [
                'controller'      => UserAboutMeAction::class,
                'method'          => 'get',
                'path'            => 'users/about_me',
                'openapi_context' => [
                    'summary'    => 'Shows info about the authenticated user',
                ],
            ],
            'auth'               => [
                'controller'      => UserAuthAction::class,
                'method'          => 'post',
                'path'            => 'users/auth',
                'openapi_context' => ['summary' => 'Authorization'],
            ],
            'authByRefreshToken' => [
                'controller'      => UserAuthByRefreshTokenAction::class,
                'method'          => 'post',
                'path'            => 'users/auth/refreshToken',
                'openapi_context' => ['summary' => 'Authorization by refreshToken'],
                'input'           => RefreshTokenRequestDto::class,
            ],
            'isUniqueEmail'      => [
                'controller'              => UserIsUniqueEmailAction::class,
                'method'                  => 'post',
                'path'                    => 'users/is_unique_email',
                'openapi_context'         => ['summary' => 'Checks email for uniqueness'],
                'denormalization_context' => ['groups' => ['user:isUniqueEmail:write']],
            ],
        ],
        itemOperations: [
            'changePassword' => [
                'controller'              => UserChangePasswordAction::class,
                'method'                  => 'put',
                'path'                    => 'users/{id}/password',
                'security'                => "object == user || is_granted('ROLE_ADMIN')",
                'openapi_context'         => ['summary' => 'Changes password'],
                'denormalization_context' => ['groups' => ['user:changePassword:write']],
            ],
            'delete'         => [
                'controller' => DeleteAction::class,
                'security'   => "object == user || is_granted('ROLE_ADMIN')",
            ],
            'get'            => [
                'security' => "object == user || is_granted('ROLE_ADMIN')",
            ],
            'put'            => [
                'security'                => "object == user || is_granted('ROLE_ADMIN')",
                'denormalization_context' => ['groups' => ['user:put:write']],
            ],
        ],
        denormalizationContext: ['groups' => ['user:write']],
        normalizationContext: ['groups' => ['user:read', 'users:read']],
    )]
    #[ApiFilter(OrderFilter::class, properties: ['id', 'createdAt', 'updatedAt', 'email'])]
    #[ApiFilter(SearchFilter::class, properties: ['id' => 'exact', 'email' => 'partial'])]
    #[UniqueEntity('email', message: 'This email is already used')]
    
    /**
     * @ORM\Entity(repositoryClass=UserRepository::class)
     */
    class User implements
        UserInterface,
        UpdatedAtSettableInterface,
        CreatedAtSettableInterface,
        IsDeletedSettableInterface
    {
        /**
         * @ORM\Id
         * @ORM\GeneratedValue
         * @ORM\Column(type="integer")
         */
        #[Groups(['users:read'])]
        private $id;
    
        /**
         * @ORM\Column(type="string", length=255)
         */
        #[Assert\Email]
        #[Groups(['users:read', 'user:write', 'user:put:write', 'user:isUniqueEmail:write'])]
        private $email;
    
        /**
         * @ORM\Column(type="string", length=255)
         */
        #[Groups(['user:write', 'user:changePassword:write'])]
        private $password;
    
        /**
         * @ORM\Column(type="array")
         */
        #[Groups(['user:read'])]
        private $roles = [];
    
        /**
         * @ORM\Column(type="datetime")
         */
        #[Groups(['user:read'])]
        private $createdAt;
    
        /**
         * @ORM\Column(type="datetime", nullable=true)
         * @Groups({"user:read"})
         */
        #[Groups(['user:read'])]
        private $updatedAt;
    
        /**
         * @ORM\Column(type="boolean")
         */
        private $isDeleted = false;
    
       // here getter and setter methods...
    }
    

    Possible Solution
    Downgrade to 2.6.3

    Additional Context
    Symfony version 5.2.6 PHP version 8.0.3 OS: tested on windows 10, Fedora 33, docker with Debian 10

    opened by kadirov 26
  • Virtual Properties

    Virtual Properties

    I need for a ressource to add an absolute link that is generated when the user asks a resource, so it can't be used in the Entity but in a Service or maybe an Event.

    It seems that there is no documentation about it. Is it possible to add virtual properties ?

    Do you have any example ?

    Thanks

    opened by Dakhtara 26
  • GraphQL / Thoughts / Possible Implementation?

    GraphQL / Thoughts / Possible Implementation?

    So as I'm researching methods of maintaining API's and so on, I stumbled upon GraphQL.

    Have you checked it out? Is this something that Api-Platform would be interested in supporting? I think it fits along most of your goals. Obviously not REST. ;)

    https://facebook.github.io/react/blog/2015/05/01/graphql-introduction.html

    Interested in your thoughts? Jarvis

    enhancement help wanted 
    opened by BallisticPain 25
  • Provide an example of using the api-platform without an ORM

    Provide an example of using the api-platform without an ORM

    Firstable, let me thank you for this impressive work!

    As written in the subject, would be nice to have an example which makes use of external services wirhout relying to Doctrine, for example the case of a middleware/broker API.

    Thanks!

    opened by paolomainardi 24
  • Error 502 on some api routes

    Error 502 on some api routes "upstream sent too big header while reading response header from upstream"

    Hello,

    i've got 502 errors on some routes of my api on my dev machine running api-platform on docker.

    The logs shows this :

    api     | 2018/11/12 13:14:09 [error] 7#7: *6 upstream sent too big header while reading response header from upstream, client: 172.18.0.2, server: , request: "GET /v2/contact_functions HTTP/1.1", upstream: "fastcgi://172.19.0.4:9000", host: "api.test:8000", referrer: "http://api.test:8000/"
    api     | 172.18.0.2 - - [12/Nov/2018:13:14:09 +0000] "GET /v2/contact_functions HTTP/1.1" 502 559 "http://api.test:8000/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36" "172.18.0.1"
    

    I'm not sure I get what's wrong, it looks like it's an nginx error. Can we add configs (it looks like fastcgi_buffers / fastcgi_buffer_size could help). Or is there a way to control these headers ?

    On some other working routes, I can see a lot of informations under Cache-Tags Headers, could it be the problem ? Again, can I do something on these headers ?

    question 
    opened by sushicodeur 23
  • Custom GET item without to have to specify id

    Custom GET item without to have to specify id

    I'm trying to create a custom action which could be called at GET /me.

    I implemented JWT token and now I want to get the user data thanks to the security storage or something else (by Bearer or something else).

    My problem is that I can't create a custom action to get just one item but without giving an ID. It seems ApiPlatform is built to always listen ID parameter for item action, when we use _api_item_operation_name.

    Action :

    <?php
    
    namespace AppBundle\Action;
    
    use AppBundle\Repository\UserRepository;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
    use Symfony\Component\Config\Definition\Exception\Exception;
    use Symfony\Component\Routing\Annotation\Route;
    use AppBundle\Entity\User;
    use Doctrine\Common\Persistence\ManagerRegistry;
    use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
    
    class UserMe
    {
        /**
         * @var UserRepository
         */
        private $repository;
    
        /**
         * UserMe constructor.
         *
         * @param UserRepository $repository
         */
        public function __construct(UserRepository $repository)
        {
            $this->repository= $repository;
        }
    
        /**
         * @Route(
         *     name="api_users_me",
         *     path="/me",
         *     defaults={"_api_resource_class"=User::class, "_api_item_operation_name"="user_me"}
         * )
         * @Method("GET")
         */
        public function __invoke($data)
        {
            // hard retrieve of an existing user in database to test
            return $this->repository->find(5);
        }
    }
    

    Result :

    {
        "@context": "/app_dev.php/contexts/Error",
        "@type": "hydra:Error",
        "hydra:title": "An error occurred",
        "hydra:description": "Invalid identifier \"\", \"id\" has not been found.",
        ...
    }
    

    If you see, I don't need to specify an "id" for my action, but API Plateform is expecting an identifier "id".

    Here my User entity :

    <?php
    
    namespace AppBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    use Symfony\Component\Security\Core\User\AdvancedUserInterface;
    use ApiPlatform\Core\Annotation\ApiResource;
    use Symfony\Component\Serializer\Annotation\Groups;
    
    /**
     * User
     *
     * @ApiResource(
     *     collectionOperations={
     *         "get"={
     *             "method"="GET",
     *             "normalization_context"={"groups"={"get_collection"}}
     *         },
     *     },
     *     itemOperations={
     *         "get"={
     *             "method"="GET",
     *             "normalization_context"={"groups"={"get_item"}}
     *         },
     *         "user_me"={
     *             "route_name"="api_users_me"
     *         },
     *     }
     * )
     * @ORM\Table(name="user")
     * @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
     */
    class User implements AdvancedUserInterface
    {
        // ...
    }
    

    In addition, here a router debug:

    ------------------------------------------- -------- -------- ------ ---------------------------------------
      Name                                        Method   Scheme   Host   Path
     ------------------------------------------- -------- -------- ------ ---------------------------------------
      _wdt                                        ANY      ANY      ANY    /_wdt/{token}
      _profiler_home                              ANY      ANY      ANY    /_profiler/
      _profiler_search                            ANY      ANY      ANY    /_profiler/search
      _profiler_search_bar                        ANY      ANY      ANY    /_profiler/search_bar
      _profiler_info                              ANY      ANY      ANY    /_profiler/info/{about}
      _profiler_phpinfo                           ANY      ANY      ANY    /_profiler/phpinfo
      _profiler_search_results                    ANY      ANY      ANY    /_profiler/{token}/search/results
      _profiler_open_file                         ANY      ANY      ANY    /_profiler/open
      _profiler                                   ANY      ANY      ANY    /_profiler/{token}
      _profiler_router                            ANY      ANY      ANY    /_profiler/{token}/router
      _profiler_exception                         ANY      ANY      ANY    /_profiler/{token}/exception
      _profiler_exception_css                     ANY      ANY      ANY    /_profiler/{token}/exception.css
      _twig_error_test                            ANY      ANY      ANY    /_error/{code}.{_format}
      api_entrypoint                              ANY      ANY      ANY    /{index}.{_format}
      api_doc                                     ANY      ANY      ANY    /docs.{_format}
      api_jsonld_context                          ANY      ANY      ANY    /contexts/{shortName}.{_format}
      api_bookmakers_get_collection               GET      ANY      ANY    /bookmakers.{_format}
      api_bookmakers_get_item                     GET      ANY      ANY    /bookmakers/{id}.{_format}
      api_categories_get_collection               GET      ANY      ANY    /categories.{_format}
      api_categories_get_item                     GET      ANY      ANY    /categories/{id}.{_format}
      api_comments_get_collection                 GET      ANY      ANY    /comments.{_format}
      api_comments_post_collection                POST     ANY      ANY    /comments.{_format}
      api_comments_get_item                       GET      ANY      ANY    /comments/{id}.{_format}
      api_feed_items_get_collection               GET      ANY      ANY    /feed_items.{_format}
      api_feed_items_post_collection              POST     ANY      ANY    /feed_items.{_format}
      api_feed_items_get_item                     GET      ANY      ANY    /feed_items/{id}.{_format}
      api_feed_items_put_item                     PUT      ANY      ANY    /feed_items/{id}.{_format}
      api_follows_get_collection                  GET      ANY      ANY    /follows.{_format}
      api_follows_post_collection                 POST     ANY      ANY    /follows.{_format}
      api_follows_get_item                        GET      ANY      ANY    /follows/{id}.{_format}
      api_follows_delete_item                     DELETE   ANY      ANY    /follows/{id}.{_format}
      api_moderation_cards_get_collection         GET      ANY      ANY    /moderation_cards.{_format}
      api_moderation_cards_get_item               GET      ANY      ANY    /moderation_cards/{id}.{_format}
      api_picks_get_collection                    GET      ANY      ANY    /picks.{_format}
      api_picks_post_collection                   POST     ANY      ANY    /picks.{_format}
      api_picks_get_item                          GET      ANY      ANY    /picks/{id}.{_format}
      api_picks_put_item                          PUT      ANY      ANY    /picks/{id}.{_format}
      api_report_picks_post_collection            POST     ANY      ANY    /report_picks.{_format}
      api_subpicks_get_collection                 GET      ANY      ANY    /subpicks.{_format}
      api_subpicks_post_collection                POST     ANY      ANY    /subpicks.{_format}
      api_subpicks_get_item                       GET      ANY      ANY    /subpicks/{id}.{_format}
      api_subpicks_put_item                       PUT      ANY      ANY    /subpicks/{id}.{_format}
      api_subpick_type_events_get_item            GET      ANY      ANY    /subpick_type_events/{id}.{_format}
      api_subpick_type_frees_get_item             GET      ANY      ANY    /subpick_type_frees/{id}.{_format}
      api_subscribed_bookmakers_get_collection    GET      ANY      ANY    /subscribed_bookmakers.{_format}
      api_subscribed_bookmakers_post_collection   POST     ANY      ANY    /subscribed_bookmakers.{_format}
      api_subscribed_bookmakers_get_item          GET      ANY      ANY    /subscribed_bookmakers/{id}.{_format}
      api_subscribed_bookmakers_delete_item       DELETE   ANY      ANY    /subscribed_bookmakers/{id}.{_format}
      api_users_get_collection                    GET      ANY      ANY    /users.{_format}
      api_users_post_collection                   POST     ANY      ANY    /users.{_format}
      api_users_get_item                          GET      ANY      ANY    /users/{id}.{_format}
      api_users_put_item                          PUT      ANY      ANY    /users/{id}.{_format}
      api_user_likes_get_collection               GET      ANY      ANY    /user_likes.{_format}
      api_user_likes_post_collection              POST     ANY      ANY    /user_likes.{_format}
      api_user_likes_get_item                     GET      ANY      ANY    /user_likes/{id}.{_format}
      api_user_likes_delete_item                  DELETE   ANY      ANY    /user_likes/{id}.{_format}
      api_login_check                             ANY      ANY      ANY    /login_check
      api_jwt_refresh_token                       ANY      ANY      ANY    /token/refresh
      api_users_me                                GET      ANY      ANY    /me
     ------------------------------------------- -------- -------- ------ ---------------------------------------
    

    The route is here.

    Documentation also generate a required "id" field, while i don't want it : Users actions

    I also tried to "cheat" by using "_api_collection_operation_name", and redapt User correctly, but i got an "Illegal offset" error even if i return an array containing the User in method __invoke($data)

    Do you have a solution ? Anyway, thanks for this useful bundle !

    opened by Sybio 23
  • Serious limitations trying to mimic schema.org hierarchy with Doctrine ORM inheritance

    Serious limitations trying to mimic schema.org hierarchy with Doctrine ORM inheritance

    Consider this scenario:

    // src/AppBundle/Entity/Thing.php
    namespace AppBundle\Entity;
    
    use Doctrine\Common\Collections\Collection;
    use Doctrine\ORM\Mapping as ORM;
    use Dunglas\ApiBundle\Annotation\Iri;
    
    /**
     * The most generic type of item.
     *
     * @see http://schema.org/Thing Documentation on Schema.org
     *
     * @ORM\MappedSuperclass
     * @Iri("http://schema.org/Thing")
     */
    abstract class Thing
    {
        /**
         * @var Collection<ImageObject> An image of the item. This can be a [URL](http://schema.org/URL) or a fully described [ImageObject](http://schema.org/ImageObject).
         *
         * @ORM\ManyToMany(targetEntity="ImageObject")
         * @Iri("http://schema.org/image")
         */
        private $image;
    }
    
    // src/AppBundle/Entity/ImageObject.php
    namespace AppBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    use Dunglas\ApiBundle\Annotation\Iri;
    
    /**
     * An image file.
     *
     * @see http://schema.org/ImageObject Documentation on Schema.org
     *
     * @ORM\Entity
     * @Iri("http://schema.org/ImageObject")
     */
    class ImageObject extends MediaObject
    {
    }
    

    (CreativeWork and MediaObject omitted for brevity)

    This breaks down whenever there's more than one entity extending from Thing (well, of course...)

    opened by teohhanhui 22
  • Does the MIT license conflicts with React BSD + patents license?

    Does the MIT license conflicts with React BSD + patents license?

    Hello,

    this question is worrying me since days and I can't find the answer: as Api platform depends on React and as React is not using a real "open source" license, what are the risks to use Api Platform or any-dependent project using React?

    I'm pretty sure the actual MIT license can't be applied because the Patents claimed by Facebook can't be canceled, to be more precise this part of MIT license can't be applied as it to deal in the Software without restriction.

    Should we notice the developer, the end users? Should we use the BSD + patents license like Facebook did?

    Mickaël

    opened by mickaelandrieu 21
  • Why I set itemUriTemplate is not work?

    Why I set itemUriTemplate is not work?

    api-platform/core: ^3.0 I want to upload images through object mapping, but I find that only images can be uploaded, and the mapping cannot be updated to your content or changed. Because the PUT method does not support uploading images, I can find a way to make the POST method have two routes to upload images. When I look up the content in the operations section, I find that there is a sub route that can be set, but when I set it as described in the tutorial, and then upload files to this sub route, The error message that the route does not exist is displayed. How should the sub route be set to take effect? My few

    // api/src/Entity/MediaObject.php
    <?php
    
    
    namespace App\Entity;
    
    use ApiPlatform\Metadata\Post;
    use ApiPlatform\Metadata\GetCollection;
    use ApiPlatform\Metadata\Get;
    use ApiPlatform\Metadata\ApiResource;
    use ApiPlatform\Metadata\ApiProperty;
    use ApiPlatform\Metadata\ApiFilter;
    use App\Controller\CreateMediaObjectAction;
    use Doctrine\ORM\Mapping as ORM;
    use Symfony\Component\HttpFoundation\File\File;
    use Symfony\Component\Serializer\Annotation\Groups;
    use Symfony\Component\Validator\Constraints as Assert;
    use Vich\UploaderBundle\Mapping\Annotation as Vich;
    
    /**
     * @ORM\Entity
     * @Vich\Uploadable
     */
    #[ApiResource(
        types: ['http://schema.org/MediaObject'],
        operations: [
            new Get(),
            new GetCollection(),
            new Post(
                controller: CreateMediaObjectAction::class,
                openapiContext: [
                    'requestBody' => [
                        'content' => [
                            'multipart/form-data' => [
                                'schema' => [
                                    'type' => 'object',
                                    'properties' => [
                                        'file' => ['type' => 'string',
                                            'format' => 'binary'
                                        ]
                                    ]
                                ]
                            ]
                        ]
                    ]
                ],
                validationContext: ['groups' => ['Default', 'media_object_create']],
                deserialize: false,
            )
        ],
        normalizationContext: ['groups' => ['media_object:read']]
    )]
    class MediaObject
    {
        /**
         * @ORM\Column(type="integer")
         * @ORM\GeneratedValue
         * @ORM\Id
         */
        private ?int $id = null;
    
        #[ApiProperty(iris: ['http://schema.org/contentUrl'])]
        #[Groups(['media_object:read'])]
        public ?string $contentUrl = null;
    
        /**
         * @Vich\UploadableField(mapping="media_object", fileNameProperty="filePath")
         */
        #[Assert\NotNull(groups: ['media_object_create'])]
        public ?File $file = null;
    
        /**
         * @ORM\Column(nullable=true)
         */
        public ?string $filePath = null;
    
        public function getId(): ?int
        {
            return $this->id;
        }
    }
    

    the Slider file is

    // api/src/Entity/Slider.php
    <?php
    
    namespace App\Entity;
    
    use ApiPlatform\Metadata\ApiResource;
    use ApiPlatform\Metadata\ApiProperty;
    use ApiPlatform\Metadata\ApiFilter;
    use ApiPlatform\Metadata\Get;
    use ApiPlatform\Metadata\GetCollection;
    use ApiPlatform\Metadata\Post;
    use ApiPlatform\Metadata\Put;
    use App\Repository\SliderRepository;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\Common\Collections\Collection;
    use Doctrine\ORM\Mapping as ORM;
    use Nette\Utils\DateTime;
    use phpDocumentor\Reflection\DocBlock\Tags\Method;
    use Symfony\Component\HttpFoundation\File\File;
    use Symfony\Component\Serializer\Annotation\Groups;
    use Vich\UploaderBundle\Mapping\Annotation as Vich;
    
    /**
     * @ORM\Entity(repositoryClass=SliderRepository::class)
     * @Vich\Uploadable
     */
    #[ApiResource(
        denormalizationContext: ['groups' => ['slider:write']],
        types: ['https://schema.org/Slider'],  
    )]
    #[GetCollection] // auto-generated path will be /users
    #[Get] // auto-generated path will be /users/{id}
    #[GetCollection(itemUriTemplate: '/sliders/{id}/image'/*, ... */)]
    #[Post(inputFormats: ['multipart' => ['multipart/form-data']],itemUriTemplate: '/sliders/{id}/image'/*, ... */)]
    #[Get(uriTemplate: '/sliders/{id}'/*, ... */)]
    class Slider
    {
        /**
         * @ORM\Id
         * @ORM\GeneratedValue
         * @ORM\Column(type="integer")
         */
        private $id;
    
        /**
         * @ORM\ManyToOne(targetEntity="MediaObject")
         * @ORM\JoinColumn(nullable=true)
         */
        #[ApiProperty(types: ['https://schema.org/mediaObject'])]
        public ?MediaObject $image = null;
    
        /**
         * @ORM\Column(type="string", length=255)
         */
        #[Groups(['slider:write'])]
        private $title;
    
    
        /**
         * @ORM\Column(type="string", length=255, nullable=true)
         */
        #[Groups(['slider:write'])]
        private $topic;
    
        /**
         * @ORM\Column(type="string", length=255, nullable=true)
         */
        #[Groups(['slider:write'])]
        private $description;
    
        /**
         * @ORM\Column(type="string", length=50)
         */
        #[Groups(['slider:write'])]
        private $state;
    
        /**
         * @ORM\Column(type="datetime")
         */
        private $createdAt;
    
        /**
         * @ORM\Column(type="datetime")
         */
        private $updatedAt;
    
        public function __construct()
        {
            $this->setCreatedAt(new DateTime('now'));
            $this->setUpdatedAt(new DateTime('now'));
        }
    
        public function getId(): ?int
        {
            return $this->id;
        }
    
        public function getTitle(): ?string
        {
            return $this->title;
        }
    
        public function setTitle(string $title): self
        {
            $this->title = $title;
    
            return $this;
        }
    
    
        public function getTopic(): ?string
        {
            return $this->topic;
        }
    
        public function setTopic(?string $topic): self
        {
            $this->topic = $topic;
    
            return $this;
        }
    
        public function getDescription(): ?string
        {
            return $this->description;
        }
    
        public function setDescription(?string $description): self
        {
            $this->description = $description;
    
            return $this;
        }
    
        public function getState(): ?string
        {
            return $this->state;
        }
    
        public function setState(string $state=null): self
        {
            if(!$state)
            {
                $this->state = 1;
            }
            $this->state = $state;
    
            return $this;
        }
    
        public function getCreatedAt(): ?\DateTimeInterface
        {
            return $this->createdAt;
        }
    
        public function setCreatedAt(\DateTimeInterface $createdAt): self
        {
            $this->createdAt = $createdAt;
    
            return $this;
        }
    
        public function getUpdatedAt(): ?\DateTimeInterface
        {
            return $this->updatedAt;
        }
    
        public function setUpdatedAt(\DateTimeInterface $updatedAt): self
        {
            $this->updatedAt = $updatedAt;
    
            return $this;
        }
    
    }
    
    <?php
    // api/src/Controller/CreateMediaObjectAction.php
    
    <?php
    
    namespace App\Controller;
    
    use App\Entity\MediaObject;
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpKernel\Attribute\AsController;
    use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
    
    #[AsController]
    final class CreateMediaObjectAction extends AbstractController
    {
        public function __invoke(Request $request): MediaObject
        {
            $uploadedFile = $request->files->get('file');
            if (!$uploadedFile) {
                throw new BadRequestHttpException('"file" is required');
            }
    
            $mediaObject = new MediaObject();
            $mediaObject->file = $uploadedFile;
    
            return $mediaObject;
        }
    }
    
    <?php
    // api/src/Serializer/MediaObjectNormalizer.php
    
    <?php
    
    
    namespace App\Serializer;
    
    use App\Entity\MediaObject;
    use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
    use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
    use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
    use Vich\UploaderBundle\Storage\StorageInterface;
    
    final class MediaObjectNormalizer implements ContextAwareNormalizerInterface,  NormalizerAwareInterface
    {
        use NormalizerAwareTrait;
    
        private const ALREADY_CALLED = 'MEDIA_OBJECT_NORMALIZER_ALREADY_CALLED';
    
        public function __construct(private StorageInterface $storage)
        {
        }
    
        public function normalize($object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
        {
            $context[self::ALREADY_CALLED] = true;
    
            $object->contentUrl = $this->storage->resolveUri($object, 'file');
    
            return $this->normalizer->normalize($object, $format, $context);
        }
    
        public function supportsNormalization($data, ?string $format = null, array $context = []): bool
        {
            if (isset($context[self::ALREADY_CALLED])) {
                return false;
            }
    
            return $data instanceof MediaObject;
        }
    }
    

    When I use the default route POST, I can create Slider and MediaObject, but I cannot update any information. When I use/sliders/{id}/image or any custom uri template route with id, I can update the information of the slider or MediaObject, but I cannot create new objects. When the route is set to the itemUriTemplate, it does not work. The php bin/console debug: route does not display the route set by the itemUriTemplate

    opened by bhtomming 0
  • create home.js

    create home.js

    | Q | A | ------------- | --- | Branch? | main for features / current stable version branch for bug fixes | Tickets | #... | License | MIT | Doc PR | api-platform/docs#...

    opened by izuuuu 0
  • #[ApiResource] attribute causing exception in OneToMay resource

    #[ApiResource] attribute causing exception in OneToMay resource

    Symfony version: 6 API platform: 3.0.6 PHP 8.1

    I have two related doctrine entities:

    Content

    #[ApiResource(operations: [new Get(), new Patch(), new Delete(), new Put(), new Post(uriTemplate: '/contents/add_text', controller: ContentTextPersist::class, read: false, openapiContext: ['description' => 'This endpoint provides a way to add content text and variants to a content object without incurring circular reference issue as a result of the nested nature of the Content -> ContentText and ContentTextVariants.', 'summary' => 'Persist new content text item and it\'s translations.']), new Post(), new GetCollection()], order: ['createdAt' => 'DESC'], normalizationContext: ['groups' => ['content:read']], denormalizationContext: ['groups' => ['content:write']], filters: ['translation.groups'])]
    #[ORM\Entity]
    #[ORM\HasLifecycleCallbacks]
    class Content extends AbstractTranslatable
    {
    	....
    	/**
         * @var \Doctrine\Common\Collections\Collection<int, \App\Entity\ContentText>|\App\Entity\ContentText[]
         */
        #[ORM\OneToMany(targetEntity: ContentText::class, mappedBy: 'content', orphanRemoval: true, cascade: ['persist'])]
        #[Groups(['content:read', 'content:write'])]
        private iterable $text;
    
        ...
        public function __construct()
        {
            parent::__construct();
            $this->text = new ArrayCollection();
        }
    
        ...
        /**
         * @return Collection|ContentText[]
         */
        public function getText() : Collection
        {
            return $this->text;
        }
        public function addText(ContentText $text) : self
        {
            if (!$this->text->contains($text)) {
                $this->text[] = $text;
                $text->setContent($this);
            }
            return $this;
        }
        public function removeText(ContentText $text) : self
        {
            if ($this->text->removeElement($text)) {
                // set the owning side to null (unless already changed)
                if ($text->getContent() === $this) {
                    $text->setContent(null);
                }
            }
            return $this;
        }
    }
    

    ContentText

    #[ApiResource(operations: [new Patch(), new Delete(), new Get(), new GetCollection()], order: ['createdAt' => 'DESC'], paginationPartial: true, normalizationContext: ['groups' => ['contentText:read']], denormalizationContext: ['groups' => ['contentText:write']], filters: ['translation.groups'])]
    #[ORM\Entity]
    #[ORM\HasLifecycleCallbacks]
    class ContentText
    {
        #[ORM\Id]
        #[ORM\GeneratedValue]
        #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
        #[Groups(['contentText:read', 'content:read'])]
        private ?int $id = null;
    
        public function getId() : ?int
        {
            return $this->id;
        }
    
    }
    

    Would anyone know why if I remove #[ApiResource] attribute from ContentText entity then I'm able to get the collection of ContentText related to Content otherwise I get the error below:

    "Unable to generate an IRI for the item of type \"App\\Entity\\ContentText\""
    
    opened by bakallandba 0
  • OpenAPI responses not overwriting defaults

    OpenAPI responses not overwriting defaults

    API Platform version(s) affected: 3.0.0

    Description

    When setting the openapiContext for some operation, it is possible to set the expected responses, using the responses key, like so:

    #[ApiResource(operations: [
      new Post(
        uriTemplate: '/some/path',
        controller: SomeAction::class,
        openapiContext: [
          'responses' => [
            '201' => [
              'description' => 'Resource created',
            ],
          ],
        ],
      )],
    )]
    

    The above correctly updates the 201 response. However, because I am setting responses to an array that only contains the 201 response, I would expect that to be the only response that is shown. However, the defaults still remain.

    Expected result

    expected

    Actual result

    actual

    How to reproduce

    1. Create a custom API operation using the above code snippet.
    2. Open the Swagger documentation.
    3. Notice that default responses still exist.

    Possible solution

    The challenge here is that the actual solution probably isn't simple. If we change the OpenAPI context handling from "merge" to "set", then that would cascade to all OpenAPI settings, effectively requiring everything to be rebuilt manually each time even a trivial change needs to be made.

    I think the best solution would be to allow settings to be manually removed by setting them to null. A quick glance over the OpenAPI Specification seems to suggest that null is not something that Swagger handles any particular way, and even if it did, we could still pass this value by using "null" as a string. This change would arguably also not introduce a BC break.

    An alternative solution would be to replace "merge" with "set", and then also add some magical '_inherit' => true option for each array, or something to that effect. But that seems weird and isn't better than the previous solution.

    Additional context

    One thing I find confusing is that an issue from 2020 seems to suggest that the behavior used to be different. I'm not sure what changed since then.

    opened by Nathanael-Shermett 0
  • Graphql variable causes runtime error

    Graphql variable causes runtime error

    API Platform version(s) affected: 3.0.0

    Description

    After updating php from 8.0 to 8.1 and symfony 5.4 to 6.1. to try to update apiplatform to version 3.0.0 I had issues such as a deleted api_platform.yaml from config/packages and a runtime error

    How to reproduce

    Update major versions of php and symfony to 8.1 and 6.1 respectively.

    Possible Solution

    I commented the line 77 and it worked. Additional Context

    image image

    opened by AbdelilahDerfoufi 0
  • Subresources : adding multiple Links to the same ApiResource doesn't work

    Subresources : adding multiple Links to the same ApiResource doesn't work

    API Platform version(s) affected: 3.0.4

    Description
    I'm trying to add an ApiResource for a relation between an ApiResource and itself, and i can't get it to give me correct item IRIs

    How to reproduce
    Create a basic resource :

    #[ApiResource(
        operations: [
            new Get(
                uriTemplate: '/resources/{id}',
                provider   : ResourceProvider::class //just returns a dummy Resource
            ),
        ]
    )]
    class Resource
    {
        #[ApiProperty(identifier: true)]
        public int $id;
    
        public string $name;
    }
    

    Then another for the relation :

    #[ApiResource(
        operations: [
            new Get(
                uriTemplate : '/resources/{first}/relations/{second}',
                uriVariables: [
                    'first' => new Link(toProperty: 'first', fromClass: Resource::class),
                    'second' => new Link(toProperty: 'second', fromClass: Resource::class),
                ],
            ),
            new GetCollection(
                uriTemplate : '/resources/{first}/relations',
                uriVariables: [
                    'first' => new Link(toProperty: 'first', fromClass: Resource::class),
                ],
                provider    : RelationProvider::class //builds a dummy Relation[] array
            ),
        ]
    )]
    class Relation
    {
        #[ApiProperty(identifier: true)]
        public Resource $first;
        #[ApiProperty(identifier: true)]
        public Resource $second;
    
        public string $something;
    }
    

    Then call /resources/1/relations. You'll get something like that :

    {
      "@context": "/contexts/Relation",
      "@id": "/resources/1/relations",
      "@type": "hydra:Collection",
      "hydra:member": [
        {
          "@id": "/resources/1/relations/1",
          "@type": "Relation",
          "first": "/resources/1",
          "second": "/resources/2",
          "something": "ok"
        }
      ],
      "hydra:totalItems": 1
    }
    

    Notice the ending id in @id is the one from "first", not the one from "second".

    Possible Solution
    No clue, i may just not understand this correctly. I tried all i could think of, but i either get this or "unable to create IRI for item...".

    opened by mrossard 0
Releases(v3.0.3)
Owner
API Platform
Build modern, hypermedia APIs with ease, generate React applications from the API documentation.
API Platform
Monorepo of the PoP project, including: a server-side component model in PHP, a GraphQL server, a GraphQL API plugin for WordPress, and a website builder

PoP PoP is a monorepo containing several projects. The GraphQL API for WordPress plugin GraphQL API for WordPress is a forward-looking and powerful Gr

Leonardo Losoviz 260 Nov 28, 2022
Monorepo of the PoP project, including: a server-side component model in PHP, a GraphQL server, a GraphQL API plugin for WordPress, and a website builder

PoP PoP is a monorepo containing several projects. The GraphQL API for WordPress plugin GraphQL API for WordPress is a forward-looking and powerful Gr

Leonardo Losoviz 261 Nov 30, 2022
Syntax to query GraphQL through URL params, which grants a GraphQL API the capability to be cached on the server.

Field Query Syntax to query GraphQL through URL params, which grants a GraphQL API the capability to be cached on the server. Install Via Composer com

PoP 4 Jan 7, 2022
The server component of API Platform: hypermedia and GraphQL APIs in minutes

API Platform Core API Platform Core is an easy to use and powerful system to create hypermedia-driven REST and GraphQL APIs. It is a component of the

API Platform 2.2k Nov 26, 2022
GraphQL API to Studio Ghibli REST API

GhibliQL GhibliQL is a GraphQL wrapper to the Studio Ghibli REST API Usage First, you'll need a GraphQL client to query GhibliQL, like GraphQL IDE Con

Sebastien Bizet 8 Nov 5, 2022
A Statamic Pro addon that provides alternative GraphQL queries for collections, entries and global sets.

Statamic Enhanced GraphQL A Statamic CMS GraphQL Addon that provides alternative GraphQL queries for collections, entries and global sets. ⚠️ This is

Grischa Erbe 2 Dec 7, 2021
This bundle provides tools to build a complete GraphQL server in your Symfony App.

OverblogGraphQLBundle This Symfony bundle provides integration of GraphQL using webonyx/graphql-php and GraphQL Relay. It also supports: batching with

Webedia - Overblog 718 Nov 15, 2022
Pure PHP implementation of GraphQL Server – Symfony Bundle

Symfony GraphQl Bundle This is a bundle based on the pure PHP GraphQL Server implementation This bundle provides you with: Full compatibility with the

null 285 Dec 1, 2022
GraphQL Bundle for Symfony 2.

Symfony 2 GraphQl Bundle Use Facebook GraphQL with Symfony 2. This library port laravel-graphql. It is based on the PHP implementation here. Installat

Sergey Varibrus 35 Nov 17, 2022
Laravel wrapper for Facebook's GraphQL

Laravel GraphQL Use Facebook's GraphQL with Laravel 6.0+. It is based on the PHP port of GraphQL reference implementation. You can find more informati

Mikk Mihkel Nurges 1.9k Nov 29, 2022
A framework for serving GraphQL from Laravel

Lighthouse A framework for serving GraphQL from Laravel Lighthouse is a GraphQL framework that integrates with your Laravel application. It takes the

NuWave Commerce 3k Dec 4, 2022
EXPERIMENTAL plugin extending WPGraphQL to support querying (Gutenberg) Blocks as data, using Server Side Block registries to map Blocks to the GraphQL Schema.

WPGraphQL Block Editor This is an experimental plugin to work toward compatiblity between the WordPress Gutenberg Block Editor and WPGraphQL, based on

WPGraphQL 29 Nov 18, 2022
🍞🧑‍🍳 An on-the-fly GraphQL Schema generator from Eloquent models for Laravel.

An on-the-fly GraphQL Schema generator from Eloquent models for Laravel. Installation Quickstart Model schemas Installation This package requires PHP

Scrn 100 Oct 16, 2022
Test your PHP GraphQL server in style, with Pest!

Pest GraphQL Plugin Test your GraphQL API in style, with Pest! Installation Simply install through Composer! composer require --dev miniaturebase/pest

Minibase 14 Aug 9, 2022
GraphQL implementation with power of Laravel

Laravel GraphQL Use Facebook GraphQL with Laravel 5.2 >=. It is based on the PHP implementation here. You can find more information about GraphQL in t

Studionet 56 Mar 9, 2022
Add Price Including tax for Magento's "cart" GraphQl query

Comwrap_GraphQlCartPrices Add Price Including tax for Magento's "cart" GraphQl query Query will looks like following: items { id __typenam

Comwrap 1 Dec 2, 2021
GraPHPinator ⚡ 🌐 ⚡ Easy-to-use & Fast GraphQL server implementation for PHP

Easy-to-use & Fast GraphQL server implementation for modern PHP. Includes features from latest draft, middleware directives and modules with extra functionality.

Infinityloop.dev 34 Oct 16, 2022
Place where I record all knowledge gained for GraphQL from Laracasts & other tutorials.

Knowledge from Laracasts series: https://laracasts.com/series/graphql-with-laravel-and-vue What is GraphQL It is a query language for your API, and it

Nikola 0 Dec 26, 2021
Pure PHP realization of GraphQL protocol

Looking for Maintainers! Unfortunatelly, we cannot longer support this package and are looking for someone to take the ownership. Currently Only PRs w

null 714 Dec 6, 2022