<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Matteo Kovačić</title>
    <description>The latest articles on Forem by Matteo Kovačić (@matteokov).</description>
    <link>https://forem.com/matteokov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F346707%2F220661b5-e203-474b-9fb8-19c2ca0b6117.jpeg</url>
      <title>Forem: Matteo Kovačić</title>
      <link>https://forem.com/matteokov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/matteokov"/>
    <language>en</language>
    <item>
      <title>5 reasons why we ended up rewriting the whole application before its release</title>
      <dc:creator>Matteo Kovačić</dc:creator>
      <pubDate>Mon, 22 Nov 2021 23:24:57 +0000</pubDate>
      <link>https://forem.com/bornfightcompany/5-reasons-why-we-ended-up-rewriting-the-whole-application-before-its-release-1dch</link>
      <guid>https://forem.com/bornfightcompany/5-reasons-why-we-ended-up-rewriting-the-whole-application-before-its-release-1dch</guid>
      <description>&lt;p&gt;As developers, sooner or later we will end up in a situation where we have to work with a legacy codebase. That can be the whole application or just part of the application written at the very start of the project. It's hard to fight legacy as we don't know how each part of the application will grow and at which scale. Making everything easy to scale is not very efficient so you need to guess which parts of the application are most likely to be improved in the future.&lt;br&gt;
But on one of our projects, we faced a unique challenge where we created a legacy even before the initial release. For quite some time we tried to fix it or improve it, but in the end, we decided that we should rewrite the whole project from scratch.&lt;/p&gt;

&lt;p&gt;Here are the top 5 reasons why we ended up doing it and changes we made in a rewrite.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Unknown territory
&lt;/h3&gt;

&lt;p&gt;Before this one, we successfully developed more than 100 projects and we thought this should be just one more of them. The initial estimate was much bigger compared to anything else we were working on before. Just a few months into the project there were already red flags. The team was not comfortable with the chosen technologies as they had little experience with them. From the project very beginning, we used agile methodology in contrast to a waterfall on our previous projects. We started with very little documentation (as agile suggests) and worked from sprint to sprint. When the project grew in size, we faced constant refactors of our previous work to introduce new features. The combination of these few problems drastically increased feature estimates.&lt;br&gt;
We also didn't have a lot of experience with the business domain, so we needed client input to discover even the basic information about their day-to-day work.&lt;/p&gt;

&lt;p&gt;To fight these challenges, a few new roles were introduced: product manager, business analyst, and tech lead. Some were given to the experienced people already on the project, but some required completely new colleagues. Also, we decided to create a project specification with all the features, and a long-term development plan. This plan enabled us to develop features with upcoming features in mind. Together with all the organizational changes, we introduced new processes and improved old ones. This was supported by new tooling (Jira, Confluence) and integrations with tools already in our stack (GitHub, CI:CD).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Technology choice
&lt;/h3&gt;

&lt;p&gt;One of the main reasons why we faced some walls during development is technology choice for a project of such size. Our core technologies were not a problem, but JSON:API as API standard gave us plenty of issues. We had a lot of actions on top of domain models and this didn't work very well with the resource-based approach. In addition to this, maintaining API documentation took us too much time. Generators helped with new resources, but not so much with modifying old ones. &lt;/p&gt;

&lt;p&gt;In a rewrite, we swapped JSON:API with GraphQL which gave us much-needed flexibility in communication between API and front-facing app. Accessing the data remained very similar to what we used to in JSON:API, but mutations allowed us to modify data in a more action-focused way. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Architecture
&lt;/h3&gt;

&lt;p&gt;We were very comfortable with MVC, so we decided to keep it in this project as well. The only difference is that we switched to a headless architecture where a back-end team develops API and a front-end team develops a front-facing app. When the project scaled, folder structure became very messy and there were no restriction to use a logic of multiple domain contexts in the same place. With everything mentioned, adding new features to such codebase became very hard even with a good code coverage in tests.&lt;/p&gt;

&lt;p&gt;This was one of the hardest challenges to fix as we had to change it from the ground up. After some research and a few prototypes, we decided to use DDD and Layered architecture principles in our codebase. It gave us a very descriptive folder structure in every bounded context and boundaries for cross-context communication. &lt;/p&gt;

&lt;h3&gt;
  
  
  4. Onboarding difficulty
&lt;/h3&gt;

&lt;p&gt;In a project with a complex domain and messy codebase with no clear structure, onboarding of new colleagues was very difficult. It took us around 3 months before a new person on the project became productive. As this was a long-term project, it's expected that from time to time we will need to onboard a new person, and losing too much time on it is something we wanted to avoid. &lt;/p&gt;

&lt;p&gt;Just by implementing a new architecture, everything mentioned here was drastically improved. We had an option to onboard people only in one specific domain context and they could finish their tasks without worrying about breaking other parts of the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Long-term motivation
&lt;/h3&gt;

&lt;p&gt;Every developer wants to work on an interesting, challenging, well-structured project with little to no stress. With everything we were facing, it was very hard to keep a good team spirit. Instead of feeling proud on finding the solution for a complex domain problem, we faced technical challenges where domain logic was very simple. This made us feel like we are not progressing as we should. &lt;/p&gt;

&lt;p&gt;Clear hierarchy, roles, and responsibilities in combination with modern technologies drastically improved team morale. To ensure it stays this way, after every sprint, we grade our happiness and try to figure out what could be improved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It's never an easy decision to abandon everything you were building for years, but sometimes this is the best possible decision. It took us 10 months to rewrite everything and during this process, we didn't face any major blockers or technical limitations. The team became more stable and fueled by the same goal. By looking at the project's future, the rewrite was the best decision we could make. The mistake was that we didn't do it earlier.&lt;/p&gt;

</description>
      <category>engineeringmonday</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How to execute multiple GraphQL mutations in a transaction using Overblog GraphQL Bundle</title>
      <dc:creator>Matteo Kovačić</dc:creator>
      <pubDate>Tue, 06 Jul 2021 11:43:26 +0000</pubDate>
      <link>https://forem.com/bornfightcompany/how-to-execute-multiple-graphql-mutations-in-a-transaction-using-overblog-graphql-bundle-1282</link>
      <guid>https://forem.com/bornfightcompany/how-to-execute-multiple-graphql-mutations-in-a-transaction-using-overblog-graphql-bundle-1282</guid>
      <description>&lt;p&gt;When we started working with GraphQL, we experienced common issues like N+1 problem, error handling, etc. As our API started growing, new challenges occurred. They were mostly related to the schema design and how to have a schema that is easy to read and also functional.&lt;/p&gt;

&lt;p&gt;By specification, GraphQL allows us to send multiple mutations in a single request and these mutations will be executed in the order in which they were sent. GraphQL specification doesn't guarantee us that if some of these mutations fail, all others will fail as well. In other words, GraphQL doesn't specify that all mutations should be executed in the transactions. Luckily, this can be easily enabled in the &lt;a href="https://github.com/overblog/GraphQLBundle"&gt;Overblog GraphQL Bundle&lt;/a&gt; using built-in events.&lt;/p&gt;

&lt;p&gt;First, in &lt;code&gt;services.yaml&lt;/code&gt;, you need to register the event listener for two events: &lt;code&gt;graphql.pre_executor&lt;/code&gt; and &lt;code&gt;graphql.post_executor&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;

    &lt;span class="s"&gt;App\EventListener\GraphQLExecutorListener&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;kernel.event_listener&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;graphql.pre_executor&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;onPreExecutor&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;kernel.event_listener&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;graphql.post_executor&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;onPostExecutor&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the event listener, you have a method for each event. In onPreExecutor we start the transaction. After mutations are resolved, in onPostExecutor we commit the changes if there are no errors or rollback if an error occurred.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GraphQLExecutorListener&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;EntityManagerInterface&lt;/span&gt; &lt;span class="nv"&gt;$entityManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;EntityManagerInterface&lt;/span&gt; &lt;span class="nv"&gt;$entityManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entityManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$entityManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;onPreExecutor&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;beginTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;onPostExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ExecutorResultEvent&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getConnection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$connection&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isTransactionActive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LogicException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Transaction not active'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getResult&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$connection&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTransactionNestingLevel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$connection&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$connection&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, all mutations should now be executed in the transaction.&lt;br&gt;
If necessary, the event listener can be extended to conditionally enable the transaction, for example using a header or some other flag. &lt;/p&gt;

</description>
      <category>engineeringmonday</category>
      <category>php</category>
      <category>graphql</category>
      <category>symfony</category>
    </item>
    <item>
      <title>Access control in GraphQL using Symfony</title>
      <dc:creator>Matteo Kovačić</dc:creator>
      <pubDate>Tue, 02 Feb 2021 20:48:24 +0000</pubDate>
      <link>https://forem.com/bornfightcompany/access-control-in-graphql-using-symfony-io</link>
      <guid>https://forem.com/bornfightcompany/access-control-in-graphql-using-symfony-io</guid>
      <description>&lt;p&gt;Authorization is part of almost any web application and controlling access to specific data is essential for application security. The same goes for GraphQL APIs and with help of &lt;a href="https://github.com/overblog/GraphQLBundle"&gt;Overblog's GraphQL Bundle&lt;/a&gt;, this can be done easily.&lt;/p&gt;

&lt;h3&gt;
  
  
  Field access control
&lt;/h3&gt;

&lt;p&gt;Every GraphQL API has at least one root type, Query. Root types are the most common place where we would want to control access by setting rules to a specific field. Some examples could be controlling access to some admin-related queries by allowing only users with &lt;code&gt;ROLE_ADMIN&lt;/code&gt; role or allowing access to &lt;code&gt;user&lt;/code&gt; query only if a requested user is currently authenticated user or has &lt;code&gt;ROLE_ADMIN&lt;/code&gt; role. This can be done using &lt;a href="https://symfony.com/doc/current/components/expression_language/syntax.html"&gt;expression language&lt;/a&gt; functions in the field configuration option called &lt;code&gt;resolve&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Using &lt;code&gt;hasRole&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This expression language function is provided by the bundle and is self-explanatory - it checks if the currently authenticated user has the role you provide as an argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;activityLog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Activity!]!"&lt;/span&gt;
        &lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@=hasRole('ROLE_ADMIN')"&lt;/span&gt;
        &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@=resolver('ActivityLog')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Using &lt;code&gt;isGranted&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This function is not documented in the &lt;a href="https://github.com/overblog/GraphQLBundle/blob/master/docs/definitions/expression-language.md"&gt;official documentation&lt;/a&gt;, but it actually exists if you look closely in the &lt;a href="https://github.com/overblog/GraphQLBundle/blob/master/src/ExpressionLanguage/ExpressionFunction/Security/IsGranted.php"&gt;codebase&lt;/a&gt;. Sometimes checking role is not sufficient and we want complex logic to determine if a user has access or not. This can be done using &lt;a href="https://symfony.com/doc/current/security/voters.html"&gt;voters&lt;/a&gt; and &lt;code&gt;isGranted&lt;/code&gt; expression language function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;User'&lt;/span&gt;
        &lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@=isGranted('user_access',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;args['id'])"&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ID!'&lt;/span&gt;
        &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@=resolver('User',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;[args['id']])"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;If you have any questions, comments or experiences with using GraphQL you'd like to share, put them in the comments section below!&lt;/p&gt;

</description>
      <category>engineeringmonday</category>
      <category>symfony</category>
      <category>php</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Using Pusher Channels with Symfony</title>
      <dc:creator>Matteo Kovačić</dc:creator>
      <pubDate>Mon, 26 Oct 2020 11:10:34 +0000</pubDate>
      <link>https://forem.com/bornfightcompany/using-pusher-channels-with-symfony-52o5</link>
      <guid>https://forem.com/bornfightcompany/using-pusher-channels-with-symfony-52o5</guid>
      <description>&lt;p&gt;In modern web applications, updating parts of the pages in real-time is a common requirement. There are many options, from more low-level open-source projects like socket.io to fully managed services like PubNub, Ably or Pusher Channels. &lt;/p&gt;

&lt;p&gt;After trying a few options, here at Bornfight we chose Pusher Channels for all our ongoing projects. Some of the key reasons are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pusher is present for a long time and Channels are their most stable and oldest product&lt;/li&gt;
&lt;li&gt;Private channels with auth per channel which allows authorization on the fly&lt;/li&gt;
&lt;li&gt;Developer friendly client and server libraries in almost every modern language&lt;/li&gt;
&lt;li&gt;Free sandbox and fair pricing for paid plans&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating a new Pusher app
&lt;/h2&gt;

&lt;p&gt;To follow this tutorial, you will need a free sandbox account which you can create &lt;a href="https://dashboard.pusher.com/accounts/sign_up" rel="noopener noreferrer"&gt;here&lt;/a&gt;. After you have your account ready, from the &lt;a href="https://dashboard.pusher.com/channels" rel="noopener noreferrer"&gt;channels dashboard&lt;/a&gt; you can create a new app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxb0z538ekcd2pcrvrrmu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxb0z538ekcd2pcrvrrmu.png" alt="New App Creation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;If you still don’t have Symfony project ready, you can initialize one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer create-project symfony/website-skeleton my_project_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pusher provides the official PHP library for interaction with its API. The library can be installed from the root project directory of your newly created project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require pusher/pusher-php-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library also has a Symfony recipe which will create a new service and add environment variables to &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;The recipe gave us a good starting point, but it’s a good practice to have service definitions only in &lt;code&gt;services.yaml&lt;/code&gt;. Following this, we can remove a file &lt;code&gt;config/packages/pusher_php_server.yaml&lt;/code&gt; generated by the recipe and add a new service to &lt;code&gt;services.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;

    &lt;span class="s"&gt;Pusher\Pusher&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;$auth_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(PUSHER_KEY)%'&lt;/span&gt;
            &lt;span class="na"&gt;$secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(PUSHER_SECRET)%'&lt;/span&gt;
            &lt;span class="na"&gt;$app_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(PUSHER_APP_ID)%'&lt;/span&gt;
            &lt;span class="na"&gt;$options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(PUSHER_CLUSTER)%'&lt;/span&gt;
                &lt;span class="na"&gt;useTLS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your .env file, the recipe already added a new pusher section with 3 environment variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;###&amp;gt; pusher/pusher-php-server ###&lt;/span&gt;
&lt;span class="nv"&gt;PUSHER_APP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;PUSHER_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;PUSHER_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="c"&gt;###&amp;lt; pusher/pusher-php-server ###&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because we also use &lt;code&gt;PUSHER_CLUSTER&lt;/code&gt;, add it at the end of the section and add values with your app credentials provided in the app dashboard under App Keys section.  &lt;/p&gt;

&lt;p&gt;After adding values, your .env file should look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;###&amp;gt; pusher/pusher-php-server ###&lt;/span&gt;
&lt;span class="nv"&gt;PUSHER_APP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12345
&lt;span class="nv"&gt;PUSHER_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-key
&lt;span class="nv"&gt;PUSHER_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-secret
&lt;span class="nv"&gt;PUSHER_CLUSTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eu
&lt;span class="c"&gt;###&amp;lt; pusher/pusher-php-server ###&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have configured your service and you can use is it with dependency injection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;To verify that everything works, you can create a simple demo page. &lt;/p&gt;

&lt;p&gt;Use a maker to create a new controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php bin/console make:controller DemoController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the newly generated controller, add the following two methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * @Route("/demo", name="demo", methods={"GET"})
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'demo/index.html.twig'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * @Route("/demo/say-hello", name="demo_say_hello", methods={"POST"})
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;sayHello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Pusher&lt;/span&gt; &lt;span class="nv"&gt;$pusher&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$pusher&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'greetings'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'new-greeting'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we only need to modify our template which should already be created by maker. In &lt;code&gt;templates/demo/index.html.twig&lt;/code&gt; add the following content and replace &lt;code&gt;your-key&lt;/code&gt; with the key you already added as &lt;code&gt;PUSHER_KEY&lt;/code&gt; environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s1"&gt;'base.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;Pusher Demo!&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"say-hello-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Say Hello!&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;javascripts&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://code.jquery.com/jquery-3.5.1.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://js.pusher.com/7.0/pusher.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;pusher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pusher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pusher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;greetings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-greeting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#say-hello-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'demo_say_hello'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your demo should be ready to go. Start Symfony built-in server with &lt;code&gt;symfony server:start&lt;/code&gt; and navigate to &lt;code&gt;/demo&lt;/code&gt; page with at least two browser windows or tabs. After clicking on the button, you should get an alert on all opened tabs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwktqgi5t98jxrvmkt2ya.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwktqgi5t98jxrvmkt2ya.gif" alt="Demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You have a lot of options when choosing technology for real-time updates and Pusher is one of them. You get a lot of low-level WebSocket issues handled out of the box, without compromising a great number of features and flexibility.&lt;/p&gt;

&lt;p&gt;If you already use Pusher, what do you think about it? Or if not, which technology do you use to send real-time updates to your clients? &lt;/p&gt;

</description>
      <category>engineeringmonday</category>
      <category>tutorial</category>
      <category>php</category>
      <category>pusher</category>
    </item>
    <item>
      <title>Uploading files with GraphQL in Symfony</title>
      <dc:creator>Matteo Kovačić</dc:creator>
      <pubDate>Mon, 03 Aug 2020 09:09:07 +0000</pubDate>
      <link>https://forem.com/bornfightcompany/uploading-files-with-graphql-in-symfony-3fap</link>
      <guid>https://forem.com/bornfightcompany/uploading-files-with-graphql-in-symfony-3fap</guid>
      <description>&lt;h3&gt;
  
  
  File uploads are a common thing in web development and you can most often see it in action when you try to change your account’s profile picture. GraphQL doesn’t have a file as a type defined by the specification, but a GraphQL community found a solution.
&lt;/h3&gt;

&lt;p&gt;There is a &lt;a href="https://github.com/jaydenseric/graphql-multipart-request-spec"&gt;spec&lt;/a&gt; that’s widely used in the Apollo ecosystem with &lt;a href="https://github.com/jaydenseric/apollo-upload-client"&gt;apollo-upload-client&lt;/a&gt; and &lt;a href="https://github.com/apollographql/apollo-server"&gt;apollo-server&lt;/a&gt;, which allows us to send multipart requests to the GraphQL server. In the Symfony world, Overblog’s &lt;a href="https://github.com/overblog/GraphQLBundle"&gt;GraphQLBundle&lt;/a&gt; is one of the most popular options with support for both Apollo and Relay. This bundle also supports file uploads out of the box by providing Upload type and resolving it to &lt;code&gt;UploadedFile&lt;/code&gt; object that’s commonly seen in Symfony when working with uploaded files.&lt;/p&gt;

&lt;h4&gt;
  
  
  Define your schema
&lt;/h4&gt;

&lt;p&gt;In this example we will work with a single Author entity that consists of 3 fields: &lt;code&gt;firstName&lt;/code&gt;, &lt;code&gt;lastName&lt;/code&gt; and &lt;code&gt;pictureFilename&lt;/code&gt;. GraphQL type definition should have exactly the same fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nx"&gt;Author&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ID!"&lt;/span&gt;
      &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"String!"&lt;/span&gt;
      &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"String!"&lt;/span&gt;
      &lt;span class="nx"&gt;profileFilename&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"String!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here, we also need only one mutation with arguments for required data and Author type as output. Instead of the &lt;code&gt;pictureFilename&lt;/code&gt; defined in Author type, here we will use &lt;code&gt;picture&lt;/code&gt; as an argument name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nx"&gt;Mutation&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;CreateAuthor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Author!'&lt;/span&gt;
        &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'@=mutation("App\\GraphQL\\Mutation\\AuthorMutation::createAuthor", [args["firstName"], args["lastName"], args["picture"]])'&lt;/span&gt;
        &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
          &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
          &lt;span class="nx"&gt;picture&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AuthorPictureUploadFile&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, we used custom type for the profile picture field and that is where most of the magic happens. Now, the only thing missing in the schema is to add a definition for it by extending the Upload type provided by bundle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nx"&gt;AuthorPictureUploadFile&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;custom&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;scalar&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;scalarType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'@=newObject("Overblog\\GraphQLBundle\\Upload\\Type\\GraphQLUploadType")'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Handling files in mutation
&lt;/h4&gt;

&lt;p&gt;Now, when we have the schema defined, we can add the mutation logic. In the Mutation type definition, we already specified our mutation class and how it will pass arguments to its &lt;code&gt;createAuthor&lt;/code&gt; method. Because the bundle will transform uploaded files to &lt;code&gt;UploadedFile&lt;/code&gt; objects before passing them to the mutation, we can easily move them to the desired folder and use the filename to populate entity data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthorMutation&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;MutationInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UploadedFile&lt;/span&gt; &lt;span class="nv"&gt;$picture&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Author&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;uniqid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author_picture_'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'.'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$picture&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;guessExtension&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$picture&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;uploadsPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$author&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;setFirstName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$firstName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$author&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;setLastName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$lastName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$author&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;setPictureFilename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$author&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$author&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;Uploading files with GraphQL is simple and easy with multipart specification widely supported by Apollo and its ecosystem. Even without the official specification regarding uploads, there are a few options available, but the one shown in this post is probably the simplest. Additionally, Symfony also has a great GraphQL bundle to provide server-side runtime with features like batching, file uploads and much more.&lt;/p&gt;




&lt;p&gt;If you have any questions, comments or experiences with using GraphQL you'd like to share, put them in the comments section below!&lt;/p&gt;

</description>
      <category>engineeringmonday</category>
      <category>symfony</category>
      <category>tutorial</category>
      <category>graphql</category>
    </item>
  </channel>
</rss>
